Clarify constructor failures and borrowed API contracts
This commit is contained in:
1 parent
23e280a6b4
commit
a7fcd69897
55 files changed
+332
-102
No files matched your search
@@ -51,7 +51,7 @@ impl Default for Engine {
|
|||||||
),
|
),
|
||||||
rules: Arc::new(RwLock::new(Arc::new(Rules {
|
rules: Arc::new(RwLock::new(Arc::new(Rules {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
matcher: ferese_config::activities::Matcher::new(&settings)
|
matcher: ferese_config::activities::Matcher::try_new(&settings)
|
||||||
.expect("built-in rules"),
|
.expect("built-in rules"),
|
||||||
}))),
|
}))),
|
||||||
monitor: None,
|
monitor: None,
|
||||||
@@ -691,7 +691,7 @@ mod tests {
|
|||||||
.spawn()
|
.spawn()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (owner, fd, _) = crate::ipc::activity_owner::inspect(child.id()).unwrap();
|
let (owner, fd, _) = crate::ipc::activity_owner::inspect(child.id()).unwrap();
|
||||||
let matched = ferese_config::activities::Matcher::new(&Default::default())
|
let matched = ferese_config::activities::Matcher::try_new(&Default::default())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.matches("cargo test", "/tmp", &[])
|
.matches("cargo test", "/tmp", &[])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -707,7 +707,7 @@ mod tests {
|
|||||||
let mut engine = Engine::default();
|
let mut engine = Engine::default();
|
||||||
let (owner, fd, _) = crate::ipc::activity_owner::inspect(std::process::id()).unwrap();
|
let (owner, fd, _) = crate::ipc::activity_owner::inspect(std::process::id()).unwrap();
|
||||||
let settings = ferese_config::activities::Settings::default();
|
let settings = ferese_config::activities::Settings::default();
|
||||||
let matched = ferese_config::activities::Matcher::new(&settings)
|
let matched = ferese_config::activities::Matcher::try_new(&settings)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.matches("cargo test", "/tmp/test", &[])
|
.matches("cargo test", "/tmp/test", &[])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -859,7 +859,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn rule_is_pinned_across_atomic_reload_and_no_raw_metadata_is_published() {
|
fn rule_is_pinned_across_atomic_reload_and_no_raw_metadata_is_published() {
|
||||||
let settings = ferese_config::activities::Settings::default();
|
let settings = ferese_config::activities::Settings::default();
|
||||||
let matched = ferese_config::activities::Matcher::new(&settings)
|
let matched = ferese_config::activities::Matcher::try_new(&settings)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.matches("cargo test --secret", "/secret/cwd", &[])
|
.matches("cargo test --secret", "/secret/cwd", &[])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -883,7 +883,7 @@ mod tests {
|
|||||||
replacement.rules[0].title = "Replacement".into();
|
replacement.rules[0].title = "Replacement".into();
|
||||||
engine.configure(
|
engine.configure(
|
||||||
&replacement,
|
&replacement,
|
||||||
ferese_config::activities::Matcher::new(&replacement).unwrap(),
|
ferese_config::activities::Matcher::try_new(&replacement).unwrap(),
|
||||||
);
|
);
|
||||||
let after = engine.store.entries().next().unwrap();
|
let after = engine.store.entries().next().unwrap();
|
||||||
assert_eq!(after.data.title, "cargo test");
|
assert_eq!(after.data.title, "cargo test");
|
||||||
|
|||||||
@@ -442,12 +442,11 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
if let Some(token) = backend.power_retry.take() {
|
if let Some(token) = backend.power_retry.take() {
|
||||||
handle.remove(token);
|
handle.remove(token);
|
||||||
}
|
}
|
||||||
backend
|
for clock in backend.presentation.values_mut() {
|
||||||
.presentation
|
clock.reset_timing();
|
||||||
.values_mut()
|
}
|
||||||
.for_each(PresentationClock::reset_timing);
|
for device in backend.devices.values_mut() {
|
||||||
backend.devices.values_mut().for_each(|device| {
|
for output in device.outputs.values_mut() {
|
||||||
device.outputs.values_mut().for_each(|output| {
|
|
||||||
output.gamma.suspend();
|
output.gamma.suspend();
|
||||||
cancel_output_timers(&handle, output);
|
cancel_output_timers(&handle, output);
|
||||||
output.surface.reset_buffers();
|
output.surface.reset_buffers();
|
||||||
@@ -456,9 +455,9 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
output.capture_texture = None;
|
output.capture_texture = None;
|
||||||
output.frame_pending = false;
|
output.frame_pending = false;
|
||||||
output.power.suspended();
|
output.power.suspended();
|
||||||
});
|
}
|
||||||
device.drm.pause();
|
device.drm.pause();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.display_presentation.clear();
|
state.display_presentation.clear();
|
||||||
@@ -2178,30 +2177,27 @@ fn send_frame_callbacks(state: &mut Ferese, output: &Output) {
|
|||||||
state.lock_frame_callbacks(output);
|
state.lock_frame_callbacks(output);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state
|
let visible_windows = state.space.elements().filter(|window| {
|
||||||
.space
|
state
|
||||||
.elements()
|
.windows
|
||||||
.filter(|window| {
|
.ids()
|
||||||
state
|
.get(*window)
|
||||||
.windows
|
.is_some_and(|id| state.window_belongs_to_output(*id, output))
|
||||||
.ids()
|
});
|
||||||
.get(*window)
|
for window in visible_windows {
|
||||||
.is_some_and(|id| state.window_belongs_to_output(*id, output))
|
window.send_frame(output, state.start_time.elapsed(), None, |surface, _| {
|
||||||
})
|
eligible.get(&surface.into()).cloned()
|
||||||
.for_each(|window| {
|
|
||||||
window.send_frame(output, state.start_time.elapsed(), None, |surface, _| {
|
|
||||||
eligible.get(&surface.into()).cloned()
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
let layers = layer_map_for_output(output)
|
let layers = layer_map_for_output(output)
|
||||||
.layers()
|
.layers()
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
layers.iter().for_each(|layer| {
|
for layer in &layers {
|
||||||
layer.send_frame(output, state.start_time.elapsed(), None, |surface, _| {
|
layer.send_frame(output, state.start_time.elapsed(), None, |surface, _| {
|
||||||
eligible.get(&surface.into()).cloned()
|
eligible.get(&surface.into()).cloned()
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
state.send_cursor_frame(output);
|
state.send_cursor_frame(output);
|
||||||
state.space.refresh();
|
state.space.refresh();
|
||||||
state.popups.cleanup();
|
state.popups.cleanup();
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
DrmNode, Duration, Ferese, HashMap, HashSet, Instant, OutputMode, Path, PresentationClock,
|
DrmNode, Duration, Ferese, HashMap, HashSet, Instant, OutputMode, Path, RegistrationToken,
|
||||||
RegistrationToken, Scale, Session, TimeoutAction, Timer, all_gpus, cancel_output_timers,
|
Scale, Session, TimeoutAction, Timer, all_gpus, cancel_output_timers, drm_device_key, lid,
|
||||||
drm_device_key, lid, monotonic_now, open_device, output_transform, refresh_connected_info,
|
monotonic_now, open_device, output_transform, refresh_connected_info, refresh_monitor_policy,
|
||||||
refresh_monitor_policy, remember_applied_configuration, remove_device, rescan_device,
|
remember_applied_configuration, remove_device, rescan_device, sync_battery_timer, transaction,
|
||||||
sync_battery_timer, transaction, validate_desired_outputs,
|
validate_desired_outputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
@@ -203,10 +203,9 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) {
|
|||||||
backend.topology.dirty = false;
|
backend.topology.dirty = false;
|
||||||
backend.output_error = None;
|
backend.output_error = None;
|
||||||
if reactivate {
|
if reactivate {
|
||||||
backend
|
for clock in backend.presentation.values_mut() {
|
||||||
.presentation
|
clock.reset_timing();
|
||||||
.values_mut()
|
}
|
||||||
.for_each(PresentationClock::reset_timing);
|
|
||||||
}
|
}
|
||||||
if let Some(token) = backend.topology.retry_timer.take() {
|
if let Some(token) = backend.topology.retry_timer.take() {
|
||||||
state.loop_handle.remove(token);
|
state.loop_handle.remove(token);
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ impl Config {
|
|||||||
let binding_modes = self.binding_modes(&input_settings, &bindings)?;
|
let binding_modes = self.binding_modes(&input_settings, &bindings)?;
|
||||||
Ok(crate::RuntimeConfig {
|
Ok(crate::RuntimeConfig {
|
||||||
activity_settings: self.activities.clone(),
|
activity_settings: self.activities.clone(),
|
||||||
activity_matcher: ferese_config::activities::Matcher::new(&self.activities)
|
activity_matcher: ferese_config::activities::Matcher::try_new(&self.activities)
|
||||||
.map_err(ConfigError::Binding)?,
|
.map_err(ConfigError::Binding)?,
|
||||||
lock_idle: self.lock_screen.validate()?,
|
lock_idle: self.lock_screen.validate()?,
|
||||||
night_light: self.night_light.validate().map_err(ConfigError::Binding)?,
|
night_light: self.night_light.validate().map_err(ConfigError::Binding)?,
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ fn panel_shader_matches_core_outlines_and_resets_for_popovers() {
|
|||||||
},
|
},
|
||||||
bounds,
|
bounds,
|
||||||
&groups,
|
&groups,
|
||||||
PixelGrid::new(scale).unwrap(),
|
PixelGrid::try_new(scale).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -896,7 +896,7 @@ mod region_geometry_tests {
|
|||||||
},
|
},
|
||||||
ferese_layout::Rect::new((320. - width) / 2., 0., width, 44.),
|
ferese_layout::Rect::new((320. - width) / 2., 0., width, 44.),
|
||||||
&[],
|
&[],
|
||||||
PixelGrid::new(scale).unwrap(),
|
PixelGrid::try_new(scale).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let presentation = crate::effects::PanelPresentation {
|
let presentation = crate::effects::PanelPresentation {
|
||||||
@@ -947,7 +947,7 @@ mod region_geometry_tests {
|
|||||||
use ferese_panel::shape::{AnchoredRadii, PixelGrid};
|
use ferese_panel::shape::{AnchoredRadii, PixelGrid};
|
||||||
for scale in [1., 1.25, 1.5, 2.] {
|
for scale in [1., 1.25, 1.5, 2.] {
|
||||||
for edge in [ferese_panel::Edge::Top, ferese_panel::Edge::Bottom] {
|
for edge in [ferese_panel::Edge::Top, ferese_panel::Edge::Bottom] {
|
||||||
let mut hub = Hub::new(Profile::default(), 34.).unwrap();
|
let mut hub = Hub::try_new(Profile::default(), 34.).unwrap();
|
||||||
for mode in [Mode::Peek, Mode::Idle] {
|
for mode in [Mode::Peek, Mode::Idle] {
|
||||||
hub.retarget(mode, [400., 300.]).unwrap();
|
hub.retarget(mode, [400., 300.]).unwrap();
|
||||||
for frame in 0..160 {
|
for frame in 0..160 {
|
||||||
@@ -977,7 +977,7 @@ mod region_geometry_tests {
|
|||||||
},
|
},
|
||||||
bounds,
|
bounds,
|
||||||
&[],
|
&[],
|
||||||
PixelGrid::new(scale).unwrap(),
|
PixelGrid::try_new(scale).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Reproduce the f32 transport and logical capture bounds used by native materials.
|
// Reproduce the f32 transport and logical capture bounds used by native materials.
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ pub enum Phase {
|
|||||||
LongBreak,
|
LongBreak,
|
||||||
}
|
}
|
||||||
impl Phase {
|
impl Phase {
|
||||||
|
#[must_use]
|
||||||
pub fn label(self) -> &'static str {
|
pub fn label(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Work => "Work",
|
Self::Work => "Work",
|
||||||
@@ -97,6 +98,7 @@ impl Session {
|
|||||||
deadline: Some(now.saturating_add(remaining_ms)),
|
deadline: Some(now.saturating_add(remaining_ms)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
#[must_use]
|
||||||
pub fn remaining(&self, now: Time) -> u64 {
|
pub fn remaining(&self, now: Time) -> u64 {
|
||||||
self.deadline
|
self.deadline
|
||||||
.map_or(self.remaining_ms, |deadline| deadline.saturating_sub(now))
|
.map_or(self.remaining_ms, |deadline| deadline.saturating_sub(now))
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ pub enum Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
#[must_use]
|
||||||
pub fn remaining_ms(&self, now: Time) -> Option<u64> {
|
pub fn remaining_ms(&self, now: Time) -> Option<u64> {
|
||||||
match self {
|
match self {
|
||||||
Self::Countdown {
|
Self::Countdown {
|
||||||
@@ -245,6 +246,7 @@ pub struct Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Activity {
|
impl Activity {
|
||||||
|
#[must_use]
|
||||||
pub fn eligible(&self, now: Time, dnd: bool) -> bool {
|
pub fn eligible(&self, now: Time, dnd: bool) -> bool {
|
||||||
now >= self.eligible_at
|
now >= self.eligible_at
|
||||||
&& match self.state {
|
&& match self.state {
|
||||||
@@ -256,6 +258,7 @@ impl Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn score(&self, now: Time, ceiling: u8) -> u16 {
|
pub fn score(&self, now: Time, ceiling: u8) -> u16 {
|
||||||
u16::from(self.data.priority.min(ceiling))
|
u16::from(self.data.priority.min(ceiling))
|
||||||
+ if now < self.eligible_at.saturating_add(BOOST_MS) {
|
+ if now < self.eligible_at.saturating_add(BOOST_MS) {
|
||||||
@@ -280,6 +283,7 @@ pub struct Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Store {
|
impl Store {
|
||||||
|
#[must_use]
|
||||||
pub fn new(session: u64) -> Self {
|
pub fn new(session: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
limits: Limits::default(),
|
limits: Limits::default(),
|
||||||
@@ -289,6 +293,7 @@ impl Store {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn session(&self) -> u64 {
|
pub fn session(&self) -> u64 {
|
||||||
self.session
|
self.session
|
||||||
}
|
}
|
||||||
@@ -297,6 +302,7 @@ impl Store {
|
|||||||
self.entries.values()
|
self.entries.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn resolve(&self, owner: Owner, local: &str) -> Option<&Id> {
|
pub fn resolve(&self, owner: Owner, local: &str) -> Option<&Id> {
|
||||||
self.entries
|
self.entries
|
||||||
.keys()
|
.keys()
|
||||||
@@ -477,6 +483,7 @@ impl Store {
|
|||||||
before != self.entries.len()
|
before != self.entries.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn next_deadline(&self, now: Time) -> Option<Time> {
|
pub fn next_deadline(&self, now: Time) -> Option<Time> {
|
||||||
self.entries
|
self.entries
|
||||||
.values()
|
.values()
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ impl VelocityTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn velocity(&self, now: Duration) -> f64 {
|
pub fn velocity(&self, now: Duration) -> f64 {
|
||||||
let Some(&(last, origin)) = self.samples.back() else {
|
let Some(&(last, origin)) = self.samples.back() else {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
@@ -87,6 +88,7 @@ impl VelocityTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn project(position: f64, velocity: f64, rate: f64) -> f64 {
|
pub fn project(position: f64, velocity: f64, rate: f64) -> f64 {
|
||||||
if !velocity.is_finite() || !(0.0..1.0).contains(&rate) || rate == 0.0 {
|
if !velocity.is_finite() || !(0.0..1.0).contains(&rate) || rate == 0.0 {
|
||||||
return position;
|
return position;
|
||||||
@@ -96,6 +98,7 @@ pub fn project(position: f64, velocity: f64, rate: f64) -> f64 {
|
|||||||
|
|
||||||
/// Nonlinear resistance, bounded by `extent`; the derivative transfers the
|
/// Nonlinear resistance, bounded by `extent`; the derivative transfers the
|
||||||
/// velocity of the displayed (resisted) position into the release spring.
|
/// velocity of the displayed (resisted) position into the release spring.
|
||||||
|
#[must_use]
|
||||||
pub fn rubber_band(distance: f64, extent: f64, resistance: f64) -> (f64, f64) {
|
pub fn rubber_band(distance: f64, extent: f64, resistance: f64) -> (f64, f64) {
|
||||||
if !distance.is_finite()
|
if !distance.is_finite()
|
||||||
|| !extent.is_finite()
|
|| !extent.is_finite()
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ impl Default for MotionSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MotionSettings {
|
impl MotionSettings {
|
||||||
|
#[must_use]
|
||||||
pub fn motion_enabled(self) -> bool {
|
pub fn motion_enabled(self) -> bool {
|
||||||
self.enabled && !self.reduced_motion
|
self.enabled && !self.reduced_motion
|
||||||
}
|
}
|
||||||
@@ -53,6 +54,7 @@ impl MotionSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Only small opacity changes should use a timed duration.
|
/// Only small opacity changes should use a timed duration.
|
||||||
|
#[must_use]
|
||||||
pub fn duration(self, milliseconds: f64) -> std::time::Duration {
|
pub fn duration(self, milliseconds: f64) -> std::time::Duration {
|
||||||
if !self.motion_enabled() {
|
if !self.motion_enabled() {
|
||||||
return std::time::Duration::ZERO;
|
return std::time::Duration::ZERO;
|
||||||
|
|||||||
@@ -74,14 +74,17 @@ pub struct AnimatedValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AnimatedValue {
|
impl AnimatedValue {
|
||||||
|
#[must_use]
|
||||||
pub fn needs_update(&self, target: f64) -> bool {
|
pub fn needs_update(&self, target: f64) -> bool {
|
||||||
self.target != target || self.is_animating()
|
self.target != target || self.is_animating()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn is_animating(&self) -> bool {
|
pub fn is_animating(&self) -> bool {
|
||||||
self.current != self.target || self.velocity != 0.0
|
self.current != self.target || self.velocity != 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn new(value: f64) -> Self {
|
pub fn new(value: f64) -> Self {
|
||||||
let value = finite_or_zero(value);
|
let value = finite_or_zero(value);
|
||||||
|
|
||||||
@@ -148,6 +151,7 @@ impl AnimatedValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AnimatedRect {
|
impl AnimatedRect {
|
||||||
|
#[must_use]
|
||||||
pub fn new(rect: Rect) -> Self {
|
pub fn new(rect: Rect) -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: rect,
|
current: rect,
|
||||||
@@ -228,6 +232,7 @@ impl AnimatedRect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn is_settled(&self, config: SpringConfig) -> bool {
|
pub fn is_settled(&self, config: SpringConfig) -> bool {
|
||||||
let config = config.normalized();
|
let config = config.normalized();
|
||||||
rect_distance(self.current, self.target) <= config.position_tolerance
|
rect_distance(self.current, self.target) <= config.position_tolerance
|
||||||
@@ -242,6 +247,7 @@ pub struct ClientSize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ClientSize {
|
impl ClientSize {
|
||||||
|
#[must_use]
|
||||||
pub fn from_rect(rect: Rect) -> Self {
|
pub fn from_rect(rect: Rect) -> Self {
|
||||||
Self {
|
Self {
|
||||||
width: (rect.width.round() as i32).max(1),
|
width: (rect.width.round() as i32).max(1),
|
||||||
@@ -279,16 +285,19 @@ impl ClientGeometry {
|
|||||||
matches_target
|
matches_target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn waiting_for_commit(&self) -> bool {
|
pub fn waiting_for_commit(&self) -> bool {
|
||||||
self.waiting_since.is_some()
|
self.waiting_since.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The hard bound for the current uninterrupted client-buffer wait.
|
/// The hard bound for the current uninterrupted client-buffer wait.
|
||||||
|
#[must_use]
|
||||||
pub fn commit_deadline(&self) -> Option<Duration> {
|
pub fn commit_deadline(&self) -> Option<Duration> {
|
||||||
self.waiting_since
|
self.waiting_since
|
||||||
.map(|started| started + CLIENT_COMMIT_TIMEOUT)
|
.map(|started| started + CLIENT_COMMIT_TIMEOUT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn timed_out(&self, now: Duration) -> bool {
|
pub fn timed_out(&self, now: Duration) -> bool {
|
||||||
self.waiting_since
|
self.waiting_since
|
||||||
.is_some_and(|started| now.saturating_sub(started) >= CLIENT_COMMIT_TIMEOUT)
|
.is_some_and(|started| now.saturating_sub(started) >= CLIENT_COMMIT_TIMEOUT)
|
||||||
@@ -329,6 +338,7 @@ struct ZoomTransition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WindowGeometry {
|
impl WindowGeometry {
|
||||||
|
#[must_use]
|
||||||
pub fn new(rect: Rect, committed_size: Option<ClientSize>) -> Self {
|
pub fn new(rect: Rect, committed_size: Option<ClientSize>) -> Self {
|
||||||
let rect = normalized_rect(rect);
|
let rect = normalized_rect(rect);
|
||||||
|
|
||||||
@@ -414,10 +424,12 @@ impl WindowGeometry {
|
|||||||
self.client.request_size(size, now).then_some(size)
|
self.client.request_size(size, now).then_some(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn is_zooming(&self) -> bool {
|
pub fn is_zooming(&self) -> bool {
|
||||||
self.zoom.is_some()
|
self.zoom.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn is_fullscreen(&self) -> bool {
|
pub fn is_fullscreen(&self) -> bool {
|
||||||
self.mode == PresentationMode::Fullscreen
|
self.mode == PresentationMode::Fullscreen
|
||||||
}
|
}
|
||||||
@@ -486,6 +498,7 @@ impl WindowGeometry {
|
|||||||
geometry_active
|
geometry_active
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn inverse_visual_point(&self, x: f64, y: f64) -> Option<(f64, f64)> {
|
pub fn inverse_visual_point(&self, x: f64, y: f64) -> Option<(f64, f64)> {
|
||||||
let visual = self.visual.current;
|
let visual = self.visual.current;
|
||||||
let (scale_x, scale_y) = self.visual_scale()?;
|
let (scale_x, scale_y) = self.visual_scale()?;
|
||||||
@@ -493,6 +506,7 @@ impl WindowGeometry {
|
|||||||
Some(((x - visual.x) / scale_x, (y - visual.y) / scale_y))
|
Some(((x - visual.x) / scale_x, (y - visual.y) / scale_y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn visual_scale(&self) -> Option<(f64, f64)> {
|
pub fn visual_scale(&self) -> Option<(f64, f64)> {
|
||||||
let source = self.client.committed_size?;
|
let source = self.client.committed_size?;
|
||||||
let visual = self.visual.current;
|
let visual = self.visual.current;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ pub struct Matched {
|
|||||||
pub group: Option<String>,
|
pub group: Option<String>,
|
||||||
}
|
}
|
||||||
impl Matcher {
|
impl Matcher {
|
||||||
pub fn new(settings: &Settings) -> Result<Self, String> {
|
pub fn try_new(settings: &Settings) -> Result<Self, String> {
|
||||||
let mut rules = Vec::new();
|
let mut rules = Vec::new();
|
||||||
for rule in &settings.rules {
|
for rule in &settings.rules {
|
||||||
let regex = regex::RegexBuilder::new(&rule.pattern)
|
let regex = regex::RegexBuilder::new(&rule.pattern)
|
||||||
@@ -167,6 +167,7 @@ fn validate_rule(rule: &Rule, regex: &Regex) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
#[must_use]
|
||||||
pub fn limits(&self) -> ferese_activities::Limits {
|
pub fn limits(&self) -> ferese_activities::Limits {
|
||||||
ferese_activities::Limits {
|
ferese_activities::Limits {
|
||||||
max_visible: self.max_visible,
|
max_visible: self.max_visible,
|
||||||
@@ -191,7 +192,7 @@ impl Settings {
|
|||||||
if self.rules.iter().any(|rule| !names.insert(&rule.name)) {
|
if self.rules.iter().any(|rule| !names.insert(&rule.name)) {
|
||||||
return Err("duplicate activity rule".into());
|
return Err("duplicate activity rule".into());
|
||||||
}
|
}
|
||||||
Matcher::new(self).map(|_| ())
|
Matcher::try_new(self).map(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,7 +398,7 @@ mod tests {
|
|||||||
let settings = Settings::default();
|
let settings = Settings::default();
|
||||||
assert!(!settings.sources.shell_hook);
|
assert!(!settings.sources.shell_hook);
|
||||||
assert!(!settings.sources.process_watch);
|
assert!(!settings.sources.process_watch);
|
||||||
let matcher = Matcher::new(&settings).unwrap();
|
let matcher = Matcher::try_new(&settings).unwrap();
|
||||||
assert!(matcher.matches("secret command", "/tmp", &[]).is_none());
|
assert!(matcher.matches("secret command", "/tmp", &[]).is_none());
|
||||||
let matched = matcher.matches("cargo test", "/tmp", &[]).unwrap();
|
let matched = matcher.matches("cargo test", "/tmp", &[]).unwrap();
|
||||||
assert_eq!(matched.title, "cargo test");
|
assert_eq!(matched.title, "cargo test");
|
||||||
|
|||||||
@@ -33,10 +33,12 @@ pub fn config_path() -> Option<PathBuf> {
|
|||||||
.map(|p| p.join("ferese/config.kdl"))
|
.map(|p| p.join("ferese/config.kdl"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn default_wallpaper() -> &'static str {
|
pub fn default_wallpaper() -> &'static str {
|
||||||
default_wallpaper_for(theme::Appearance::Dark)
|
default_wallpaper_for(theme::Appearance::Dark)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn default_wallpaper_for(appearance: theme::Appearance) -> &'static str {
|
pub fn default_wallpaper_for(appearance: theme::Appearance) -> &'static str {
|
||||||
static LIGHT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
static LIGHT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
||||||
static DARK: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
static DARK: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
||||||
@@ -292,11 +294,13 @@ impl Document {
|
|||||||
Ok(Self { doc, value })
|
Ok(Self { doc, value })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn value(&self) -> &Value {
|
pub fn value(&self) -> &Value {
|
||||||
&self.value
|
&self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A runtime view; the original KDL remains available for comment-preserving edits.
|
/// A runtime view; the original KDL remains available for comment-preserving edits.
|
||||||
|
#[must_use]
|
||||||
pub fn with_theme(&self, theme: &theme::ResolvedTheme) -> Self {
|
pub fn with_theme(&self, theme: &theme::ResolvedTheme) -> Self {
|
||||||
let mut document = self.clone();
|
let mut document = self.clone();
|
||||||
document.value["theme"] =
|
document.value["theme"] =
|
||||||
@@ -305,6 +309,7 @@ impl Document {
|
|||||||
document
|
document
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn get(&self, path: &str) -> Option<&Value> {
|
pub fn get(&self, path: &str) -> Option<&Value> {
|
||||||
let mut value = &self.value;
|
let mut value = &self.value;
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub const BUILTINS: [(&str, &str, &str, &str); 6] = [
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn family_id(preset: &str) -> &str {
|
pub fn family_id(preset: &str) -> &str {
|
||||||
BUILTINS
|
BUILTINS
|
||||||
.iter()
|
.iter()
|
||||||
@@ -37,6 +38,7 @@ pub fn family_id(preset: &str) -> &str {
|
|||||||
.map_or(preset, |row| row.0)
|
.map_or(preset, |row| row.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn variant(id: &str, appearance: Appearance) -> Option<&'static str> {
|
pub fn variant(id: &str, appearance: Appearance) -> Option<&'static str> {
|
||||||
if let Some(row) = BUILTINS.iter().find(|row| row.0 == id) {
|
if let Some(row) = BUILTINS.iter().find(|row| row.0 == id) {
|
||||||
return Some(if appearance == Appearance::Light {
|
return Some(if appearance == Appearance::Light {
|
||||||
@@ -54,6 +56,7 @@ pub fn variant(id: &str, appearance: Appearance) -> Option<&'static str> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn builtins() -> Vec<Family> {
|
pub fn builtins() -> Vec<Family> {
|
||||||
BUILTINS
|
BUILTINS
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ impl ConfigFile {
|
|||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn source(&self) -> &str {
|
pub fn source(&self) -> &str {
|
||||||
&self.source
|
&self.source
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ impl Default for Policy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn default_tokens() -> Tokens {
|
pub fn default_tokens() -> Tokens {
|
||||||
serde_json::from_value(json!({
|
serde_json::from_value(json!({
|
||||||
"colors": {"surface_base":"#111821", "surface_raised":"#1E2530", "application_background":"#171E27", "text_primary":"#F4F7FB", "text_muted":"#8793A2",
|
"colors": {"surface_base":"#111821", "surface_raised":"#1E2530", "application_background":"#171E27", "text_primary":"#F4F7FB", "text_muted":"#8793A2",
|
||||||
@@ -224,6 +225,7 @@ pub fn default_tokens() -> Tokens {
|
|||||||
.expect("valid built-in theme")
|
.expect("valid built-in theme")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn default_theme() -> ResolvedTheme {
|
pub fn default_theme() -> ResolvedTheme {
|
||||||
let mut tokens = default_tokens();
|
let mut tokens = default_tokens();
|
||||||
resolve_gradients(&mut tokens, None);
|
resolve_gradients(&mut tokens, None);
|
||||||
@@ -527,6 +529,7 @@ pub fn resolve_with_context(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn theme_path(directory: &Path, path: &Path) -> PathBuf {
|
pub fn theme_path(directory: &Path, path: &Path) -> PathBuf {
|
||||||
let path = if let Some(tail) = path.to_str().and_then(|path| path.strip_prefix("~/")) {
|
let path = if let Some(tail) = path.to_str().and_then(|path| path.strip_prefix("~/")) {
|
||||||
std::env::var_os("HOME")
|
std::env::var_os("HOME")
|
||||||
@@ -690,6 +693,7 @@ pub fn preset(name: &str, appearance: Appearance) -> Result<Tokens, String> {
|
|||||||
Ok(t)
|
Ok(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn authored_warnings(tokens: &Tokens) -> Vec<String> {
|
pub fn authored_warnings(tokens: &Tokens) -> Vec<String> {
|
||||||
if let Err(error) = validate(tokens) {
|
if let Err(error) = validate(tokens) {
|
||||||
return vec![error];
|
return vec![error];
|
||||||
|
|||||||
@@ -50,11 +50,13 @@ pub struct FocusCycle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FocusCycle {
|
impl FocusCycle {
|
||||||
|
#[must_use]
|
||||||
pub fn new(windows: Vec<WindowId>, current: Option<WindowId>) -> Self {
|
pub fn new(windows: Vec<WindowId>, current: Option<WindowId>) -> Self {
|
||||||
let selected = current.filter(|id| windows.contains(id));
|
let selected = current.filter(|id| windows.contains(id));
|
||||||
Self { windows, selected }
|
Self { windows, selected }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn windows(&self) -> &[WindowId] {
|
pub fn windows(&self) -> &[WindowId] {
|
||||||
&self.windows
|
&self.windows
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub struct OutputGeometry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OutputGeometry {
|
impl OutputGeometry {
|
||||||
|
#[must_use]
|
||||||
pub const fn new(x: i32, y: i32, width: i32, height: i32) -> Self {
|
pub const fn new(x: i32, y: i32, width: i32, height: i32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x,
|
x,
|
||||||
@@ -223,6 +224,7 @@ impl OutputWorkspaceMap {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn assigned_workspaces(
|
pub fn assigned_workspaces(
|
||||||
&self,
|
&self,
|
||||||
output: OutputId,
|
output: OutputId,
|
||||||
@@ -233,22 +235,27 @@ impl OutputWorkspaceMap {
|
|||||||
.flat_map(|state| state.workspaces.iter().copied())
|
.flat_map(|state| state.workspaces.iter().copied())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn focused_output(&self) -> Option<OutputId> {
|
pub fn focused_output(&self) -> Option<OutputId> {
|
||||||
self.focused
|
self.focused
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn active_workspace(&self, output: OutputId) -> Option<WorkspaceId> {
|
pub fn active_workspace(&self, output: OutputId) -> Option<WorkspaceId> {
|
||||||
self.outputs.get(&output).map(|state| state.active)
|
self.outputs.get(&output).map(|state| state.active)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn previous_workspace(&self, output: OutputId) -> Option<WorkspaceId> {
|
pub fn previous_workspace(&self, output: OutputId) -> Option<WorkspaceId> {
|
||||||
self.outputs.get(&output).and_then(|state| state.previous)
|
self.outputs.get(&output).and_then(|state| state.previous)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn output_for_workspace(&self, workspace: WorkspaceId) -> Option<OutputId> {
|
pub fn output_for_workspace(&self, workspace: WorkspaceId) -> Option<OutputId> {
|
||||||
self.assignments.get(&workspace).copied()
|
self.assignments.get(&workspace).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn home_output(&self, workspace: WorkspaceId) -> Option<OutputId> {
|
pub fn home_output(&self, workspace: WorkspaceId) -> Option<OutputId> {
|
||||||
self.homes.get(&workspace).copied()
|
self.homes.get(&workspace).copied()
|
||||||
}
|
}
|
||||||
@@ -295,6 +302,7 @@ impl OutputWorkspaceMap {
|
|||||||
self.outputs.keys().copied()
|
self.outputs.keys().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn geometry(&self, output: OutputId) -> Option<OutputGeometry> {
|
pub fn geometry(&self, output: OutputId) -> Option<OutputGeometry> {
|
||||||
self.outputs.get(&output).map(|state| state.geometry)
|
self.outputs.get(&output).map(|state| state.geometry)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ impl Default for WorkspaceLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WorkspaceLayout {
|
impl WorkspaceLayout {
|
||||||
|
#[must_use]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
mode: LayoutMode,
|
mode: LayoutMode,
|
||||||
default_column_width: ColumnWidth,
|
default_column_width: ColumnWidth,
|
||||||
@@ -61,6 +62,7 @@ impl WorkspaceLayout {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn mode(&self) -> LayoutMode {
|
pub fn mode(&self) -> LayoutMode {
|
||||||
match self {
|
match self {
|
||||||
Self::Scrolling(_) => LayoutMode::Scrolling,
|
Self::Scrolling(_) => LayoutMode::Scrolling,
|
||||||
@@ -68,6 +70,7 @@ impl WorkspaceLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn viewport_x(&self) -> Option<f64> {
|
pub fn viewport_x(&self) -> Option<f64> {
|
||||||
match self {
|
match self {
|
||||||
Self::Scrolling(layout) => Some(layout.viewport_x()),
|
Self::Scrolling(layout) => Some(layout.viewport_x()),
|
||||||
@@ -75,6 +78,7 @@ impl WorkspaceLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn preferred_window(&self) -> Option<WindowId> {
|
pub fn preferred_window(&self) -> Option<WindowId> {
|
||||||
match self {
|
match self {
|
||||||
Self::Scrolling(layout) => layout.active_window(),
|
Self::Scrolling(layout) => layout.active_window(),
|
||||||
@@ -82,6 +86,7 @@ impl WorkspaceLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn contains(&self, window: WindowId) -> bool {
|
pub fn contains(&self, window: WindowId) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Scrolling(layout) => layout.contains(window),
|
Self::Scrolling(layout) => layout.contains(window),
|
||||||
@@ -125,6 +130,7 @@ impl WorkspaceLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn tab_group(&self, window: WindowId) -> Option<ferese_layout::TabGroup> {
|
pub fn tab_group(&self, window: WindowId) -> Option<ferese_layout::TabGroup> {
|
||||||
match self {
|
match self {
|
||||||
Self::Tree(tree) => tree.tab_group(window),
|
Self::Tree(tree) => tree.tab_group(window),
|
||||||
@@ -132,6 +138,7 @@ impl WorkspaceLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn adjacent_tab(&self, window: WindowId, previous: bool) -> Option<WindowId> {
|
pub fn adjacent_tab(&self, window: WindowId, previous: bool) -> Option<WindowId> {
|
||||||
match self {
|
match self {
|
||||||
Self::Tree(tree) => tree.adjacent_tab(window, previous),
|
Self::Tree(tree) => tree.adjacent_tab(window, previous),
|
||||||
@@ -445,6 +452,7 @@ impl Default for WorkspaceSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WorkspaceSet {
|
impl WorkspaceSet {
|
||||||
|
#[must_use]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
default_layout_mode: LayoutMode,
|
default_layout_mode: LayoutMode,
|
||||||
default_column_width: ColumnWidth,
|
default_column_width: ColumnWidth,
|
||||||
@@ -562,10 +570,12 @@ impl WorkspaceSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn active_id(&self) -> WorkspaceId {
|
pub fn active_id(&self) -> WorkspaceId {
|
||||||
self.active
|
self.active
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn active(&self) -> &Workspace {
|
pub fn active(&self) -> &Workspace {
|
||||||
self.workspaces
|
self.workspaces
|
||||||
.get(&self.active)
|
.get(&self.active)
|
||||||
@@ -596,6 +606,7 @@ impl WorkspaceSet {
|
|||||||
Ok(visible_floating.or(self.active().fullscreen).or(focused))
|
Ok(visible_floating.or(self.active().fullscreen).or(focused))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn workspace(&self, id: WorkspaceId) -> Option<&Workspace> {
|
pub fn workspace(&self, id: WorkspaceId) -> Option<&Workspace> {
|
||||||
self.workspaces.get(&id)
|
self.workspaces.get(&id)
|
||||||
}
|
}
|
||||||
@@ -608,10 +619,12 @@ impl WorkspaceSet {
|
|||||||
self.workspaces.values()
|
self.workspaces.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn workspace_for_window(&self, window: WindowId) -> Option<WorkspaceId> {
|
pub fn workspace_for_window(&self, window: WindowId) -> Option<WorkspaceId> {
|
||||||
self.window_workspaces.get(&window).copied()
|
self.window_workspaces.get(&window).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn placement(&self, window: WindowId) -> Option<WindowPlacement> {
|
pub fn placement(&self, window: WindowId) -> Option<WindowPlacement> {
|
||||||
self.placements.get(&window).copied()
|
self.placements.get(&window).copied()
|
||||||
}
|
}
|
||||||
@@ -823,6 +836,7 @@ impl WorkspaceSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reserve workspace identities in a pure topology plan without creating them.
|
/// Reserve workspace identities in a pure topology plan without creating them.
|
||||||
|
#[must_use]
|
||||||
pub fn next_workspace_id(&self) -> WorkspaceId {
|
pub fn next_workspace_id(&self) -> WorkspaceId {
|
||||||
WorkspaceId(self.next_id)
|
WorkspaceId(self.next_id)
|
||||||
}
|
}
|
||||||
@@ -1210,10 +1224,12 @@ fn finite_or_zero(value: f64) -> f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
|
#[must_use]
|
||||||
pub fn window_count(&self) -> usize {
|
pub fn window_count(&self) -> usize {
|
||||||
self.layout.window_ids().count() + self.floating.len()
|
self.layout.window_ids().count() + self.floating.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.layout.window_ids().next().is_none() && self.floating.is_empty()
|
self.layout.window_ids().next().is_none() && self.floating.is_empty()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub struct WorkspaceView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OutputWorkspaceMap {
|
impl OutputWorkspaceMap {
|
||||||
|
#[must_use]
|
||||||
pub fn workspace_views(&self, workspaces: &WorkspaceSet) -> Vec<WorkspaceView> {
|
pub fn workspace_views(&self, workspaces: &WorkspaceSet) -> Vec<WorkspaceView> {
|
||||||
self.workspace_views_iter(workspaces).collect()
|
self.workspace_views_iter(workspaces).collect()
|
||||||
}
|
}
|
||||||
@@ -47,6 +48,7 @@ impl OutputWorkspaceMap {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn ordered_workspace_ids<'a>(
|
pub fn ordered_workspace_ids<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
workspaces: &'a WorkspaceSet,
|
workspaces: &'a WorkspaceSet,
|
||||||
@@ -56,6 +58,7 @@ impl OutputWorkspaceMap {
|
|||||||
.filter(|id| workspaces.workspace(*id).is_some())
|
.filter(|id| workspaces.workspace(*id).is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn ordered_workspaces(
|
pub fn ordered_workspaces(
|
||||||
&self,
|
&self,
|
||||||
workspaces: &WorkspaceSet,
|
workspaces: &WorkspaceSet,
|
||||||
@@ -66,6 +69,7 @@ impl OutputWorkspaceMap {
|
|||||||
|
|
||||||
/// Out-of-range numeric selection resolves to the trailing spare. Looking
|
/// Out-of-range numeric selection resolves to the trailing spare. Looking
|
||||||
/// up a gesture target never creates workspaces or changes history.
|
/// up a gesture target never creates workspaces or changes history.
|
||||||
|
#[must_use]
|
||||||
pub fn workspace_at(
|
pub fn workspace_at(
|
||||||
&self,
|
&self,
|
||||||
workspaces: &WorkspaceSet,
|
workspaces: &WorkspaceSet,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
pub const VERSION: u32 = 1;
|
pub const VERSION: u32 = 1;
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn now_ms() -> u64 {
|
pub fn now_ms() -> u64 {
|
||||||
let mut time = libc::timespec {
|
let mut time = libc::timespec {
|
||||||
tv_sec: 0,
|
tv_sec: 0,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ impl Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Snapshot {
|
impl Snapshot {
|
||||||
|
#[must_use]
|
||||||
pub fn changes_since(&self, previous: &Self) -> Vec<Change> {
|
pub fn changes_since(&self, previous: &Self) -> Vec<Change> {
|
||||||
let mut changes = Vec::new();
|
let mut changes = Vec::new();
|
||||||
if self.outputs != previous.outputs {
|
if self.outputs != previous.outputs {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ pub struct ResponseError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
|
#[must_use]
|
||||||
pub fn success(id: u64, result: Value) -> Self {
|
pub fn success(id: u64, result: Value) -> Self {
|
||||||
Self {
|
Self {
|
||||||
version: VERSION,
|
version: VERSION,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn now_us() -> u64 {
|
pub fn now_us() -> u64 {
|
||||||
let mut time = libc::timespec {
|
let mut time = libc::timespec {
|
||||||
tv_sec: 0,
|
tv_sec: 0,
|
||||||
@@ -79,6 +80,7 @@ impl Default for Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
|
#[must_use]
|
||||||
pub fn label(&self) -> &str {
|
pub fn label(&self) -> &str {
|
||||||
if self.title.is_empty() {
|
if self.title.is_empty() {
|
||||||
&self.identity
|
&self.identity
|
||||||
@@ -87,6 +89,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn can_toggle(&self) -> bool {
|
pub fn can_toggle(&self) -> bool {
|
||||||
self.can_control
|
self.can_control
|
||||||
&& if self.status == Playback::Playing {
|
&& if self.status == Playback::Playing {
|
||||||
@@ -96,6 +99,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn position_at(&self, now_us: u64) -> Option<u64> {
|
pub fn position_at(&self, now_us: u64) -> Option<u64> {
|
||||||
let mut position = self.position_us? as f64;
|
let mut position = self.position_us? as f64;
|
||||||
if self.status == Playback::Playing && self.rate.is_finite() {
|
if self.status == Playback::Playing && self.rate.is_finite() {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub enum State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
#[must_use]
|
||||||
pub fn is_inactive(self) -> bool {
|
pub fn is_inactive(self) -> bool {
|
||||||
matches!(self, Self::Disabled | Self::Idle | Self::Stopped)
|
matches!(self, Self::Disabled | Self::Idle | Self::Stopped)
|
||||||
}
|
}
|
||||||
@@ -51,6 +52,7 @@ impl Status {
|
|||||||
serde_json::from_value(value)
|
serde_json::from_value(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn to_value(&self) -> Value {
|
pub fn to_value(&self) -> Value {
|
||||||
match serde_json::to_value(self) {
|
match serde_json::to_value(self) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ pub struct Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Rect {
|
impl Rect {
|
||||||
|
#[must_use]
|
||||||
pub const fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
|
pub const fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x,
|
x,
|
||||||
@@ -165,14 +166,17 @@ pub struct LayoutTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutTree {
|
impl LayoutTree {
|
||||||
|
#[must_use]
|
||||||
pub fn root(&self) -> Option<NodeId> {
|
pub fn root(&self) -> Option<NodeId> {
|
||||||
self.root
|
self.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn node(&self, id: NodeId) -> Option<&Node> {
|
pub fn node(&self, id: NodeId) -> Option<&Node> {
|
||||||
self.nodes.get(&id)
|
self.nodes.get(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn contains(&self, window: WindowId) -> bool {
|
pub fn contains(&self, window: WindowId) -> bool {
|
||||||
self.windows.contains_key(&window)
|
self.windows.contains_key(&window)
|
||||||
}
|
}
|
||||||
@@ -181,6 +185,7 @@ impl LayoutTree {
|
|||||||
self.windows.keys().copied()
|
self.windows.keys().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn visible_window_count(&self) -> usize {
|
pub fn visible_window_count(&self) -> usize {
|
||||||
fn count(tree: &LayoutTree, node: NodeId) -> usize {
|
fn count(tree: &LayoutTree, node: NodeId) -> usize {
|
||||||
match tree.nodes.get(&node) {
|
match tree.nodes.get(&node) {
|
||||||
@@ -197,6 +202,7 @@ impl LayoutTree {
|
|||||||
self.root.map_or(0, |root| count(self, root))
|
self.root.map_or(0, |root| count(self, root))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn preferred_window(&self) -> Option<WindowId> {
|
pub fn preferred_window(&self) -> Option<WindowId> {
|
||||||
let leaf = self.first_window(self.root?).ok()?;
|
let leaf = self.first_window(self.root?).ok()?;
|
||||||
match self.nodes.get(&leaf)? {
|
match self.nodes.get(&leaf)? {
|
||||||
@@ -424,10 +430,12 @@ impl LayoutTree {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn tab_group(&self, window: WindowId) -> Option<TabGroup> {
|
pub fn tab_group(&self, window: WindowId) -> Option<TabGroup> {
|
||||||
self.group_at(*self.parents.get(self.windows.get(&window)?)?)
|
self.group_at(*self.parents.get(self.windows.get(&window)?)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn tab_groups(&self) -> Vec<TabGroup> {
|
pub fn tab_groups(&self) -> Vec<TabGroup> {
|
||||||
let mut groups: Vec<_> = self
|
let mut groups: Vec<_> = self
|
||||||
.nodes
|
.nodes
|
||||||
@@ -438,6 +446,7 @@ impl LayoutTree {
|
|||||||
groups
|
groups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn adjacent_tab(&self, window: WindowId, previous: bool) -> Option<WindowId> {
|
pub fn adjacent_tab(&self, window: WindowId, previous: bool) -> Option<WindowId> {
|
||||||
let group = self.tab_group(window)?;
|
let group = self.tab_group(window)?;
|
||||||
let index = group.windows.iter().position(|id| *id == window)?;
|
let index = group.windows.iter().position(|id| *id == window)?;
|
||||||
|
|||||||
@@ -90,13 +90,15 @@ impl Column {
|
|||||||
if self.heights.len() != self.windows.len() {
|
if self.heights.len() != self.windows.len() {
|
||||||
self.heights.resize(self.windows.len(), 1.0);
|
self.heights.resize(self.windows.len(), 1.0);
|
||||||
}
|
}
|
||||||
self.heights.iter_mut().for_each(|height| {
|
for height in &mut self.heights {
|
||||||
if !height.is_finite() || *height <= 0.0 {
|
if !height.is_finite() || *height <= 0.0 {
|
||||||
*height = 1.0;
|
*height = 1.0;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
let total = self.heights.iter().sum::<f64>();
|
let total = self.heights.iter().sum::<f64>();
|
||||||
self.heights.iter_mut().for_each(|height| *height /= total);
|
for height in &mut self.heights {
|
||||||
|
*height /= total;
|
||||||
|
}
|
||||||
self.active = self.active.min(self.windows.len() - 1);
|
self.active = self.active.min(self.windows.len() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,6 +168,7 @@ impl ViewportTarget {
|
|||||||
|
|
||||||
/// Re-evaluate the same target with pending allocated widths held back.
|
/// Re-evaluate the same target with pending allocated widths held back.
|
||||||
/// A start-relative request can also use the previous held target.
|
/// A start-relative request can also use the previous held target.
|
||||||
|
#[must_use]
|
||||||
pub fn with_held_widths(&self, widths: &[(WindowId, f64, f64)], start: Option<f64>) -> f64 {
|
pub fn with_held_widths(&self, widths: &[(WindowId, f64, f64)], start: Option<f64>) -> f64 {
|
||||||
let replacements = widths
|
let replacements = widths
|
||||||
.iter()
|
.iter()
|
||||||
@@ -181,6 +184,7 @@ impl ViewportTarget {
|
|||||||
self.target(&replacements, start)
|
self.target(&replacements, start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn uses_previous_target(&self) -> bool {
|
pub fn uses_previous_target(&self) -> bool {
|
||||||
matches!(self.request, ViewportRequest::Slide(..))
|
matches!(self.request, ViewportRequest::Slide(..))
|
||||||
|| (matches!(self.request, ViewportRequest::Reveal(..))
|
|| (matches!(self.request, ViewportRequest::Reveal(..))
|
||||||
@@ -290,6 +294,7 @@ impl ScrollingLayout {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn tab_group(&self, window: WindowId) -> Option<crate::TabGroup> {
|
pub fn tab_group(&self, window: WindowId) -> Option<crate::TabGroup> {
|
||||||
let (index, _) = self.window_location(window)?;
|
let (index, _) = self.window_location(window)?;
|
||||||
let column = &self.columns[index];
|
let column = &self.columns[index];
|
||||||
@@ -301,6 +306,7 @@ impl ScrollingLayout {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn adjacent_tab(&self, window: WindowId, previous: bool) -> Option<WindowId> {
|
pub fn adjacent_tab(&self, window: WindowId, previous: bool) -> Option<WindowId> {
|
||||||
let group = self.tab_group(window)?;
|
let group = self.tab_group(window)?;
|
||||||
let index = group.windows.iter().position(|id| *id == window)?;
|
let index = group.windows.iter().position(|id| *id == window)?;
|
||||||
@@ -308,6 +314,7 @@ impl ScrollingLayout {
|
|||||||
Some(group.windows[(index + step) % group.windows.len()])
|
Some(group.windows[(index + step) % group.windows.len()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn visible_window_count(&self) -> usize {
|
pub fn visible_window_count(&self) -> usize {
|
||||||
self.columns
|
self.columns
|
||||||
.iter()
|
.iter()
|
||||||
@@ -325,6 +332,7 @@ impl ScrollingLayout {
|
|||||||
self.default_width = normalized_width(width);
|
self.default_width = normalized_width(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn with_default_width(default_width: ColumnWidth) -> Self {
|
pub fn with_default_width(default_width: ColumnWidth) -> Self {
|
||||||
Self {
|
Self {
|
||||||
default_width: normalized_width(default_width),
|
default_width: normalized_width(default_width),
|
||||||
@@ -337,33 +345,40 @@ impl ScrollingLayout {
|
|||||||
self.request_focus(self.active_window().map(PendingFocusRequest::Reveal));
|
self.request_focus(self.active_window().map(PendingFocusRequest::Reveal));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn default_width(&self) -> ColumnWidth {
|
pub fn default_width(&self) -> ColumnWidth {
|
||||||
self.default_width
|
self.default_width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn columns(&self) -> &[Column] {
|
pub fn columns(&self) -> &[Column] {
|
||||||
&self.columns
|
&self.columns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn active_column(&self) -> Option<usize> {
|
pub fn active_column(&self) -> Option<usize> {
|
||||||
self.active_column
|
self.active_column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn active_window(&self) -> Option<WindowId> {
|
pub fn active_window(&self) -> Option<WindowId> {
|
||||||
let column = self.columns.get(self.active_column?)?;
|
let column = self.columns.get(self.active_column?)?;
|
||||||
column.windows.get(column.active).copied()
|
column.windows.get(column.active).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn viewport_x(&self) -> f64 {
|
pub fn viewport_x(&self) -> f64 {
|
||||||
self.viewport_x
|
self.viewport_x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn allocated_column_width(&self, window: WindowId) -> Option<f64> {
|
pub fn allocated_column_width(&self, window: WindowId) -> Option<f64> {
|
||||||
self.allocated_widths.get(&window).copied()
|
self.allocated_widths.get(&window).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this target changes when the pending column width is held back.
|
/// Whether this target changes when the pending column width is held back.
|
||||||
/// Allocated widths, rather than cell-grid/native buffer sizes, own the strip.
|
/// Allocated widths, rather than cell-grid/native buffer sizes, own the strip.
|
||||||
|
#[must_use]
|
||||||
pub fn viewport_depends_on_width(&self, window: WindowId, source: f64, target: f64) -> bool {
|
pub fn viewport_depends_on_width(&self, window: WindowId, source: f64, target: f64) -> bool {
|
||||||
(source - target).abs() > 0.001
|
(source - target).abs() > 0.001
|
||||||
&& self
|
&& self
|
||||||
@@ -373,6 +388,7 @@ impl ScrollingLayout {
|
|||||||
.is_some_and(|held| (held - self.viewport_x).abs() > 0.001)
|
.is_some_and(|held| (held - self.viewport_x).abs() > 0.001)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn viewport_target(&self) -> Option<&ViewportTarget> {
|
pub fn viewport_target(&self) -> Option<&ViewportTarget> {
|
||||||
self.viewport_basis.as_ref()
|
self.viewport_basis.as_ref()
|
||||||
}
|
}
|
||||||
@@ -400,6 +416,7 @@ impl ScrollingLayout {
|
|||||||
self.viewport_basis = Some(next);
|
self.viewport_basis = Some(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn contains(&self, window: WindowId) -> bool {
|
pub fn contains(&self, window: WindowId) -> bool {
|
||||||
self.window_location(window).is_some()
|
self.window_location(window).is_some()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub enum Presentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Presentation {
|
impl Presentation {
|
||||||
|
#[must_use]
|
||||||
pub fn style(self) -> Option<PanelStyle> {
|
pub fn style(self) -> Option<PanelStyle> {
|
||||||
match self {
|
match self {
|
||||||
Self::Floating => Some(PanelStyle::Floating),
|
Self::Floating => Some(PanelStyle::Floating),
|
||||||
@@ -48,6 +49,7 @@ pub struct Adaptive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Adaptive {
|
impl Adaptive {
|
||||||
|
#[must_use]
|
||||||
pub fn new(state: WindowState) -> Self {
|
pub fn new(state: WindowState) -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: state.into(),
|
current: state.into(),
|
||||||
@@ -55,6 +57,7 @@ impl Adaptive {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn current(&self) -> Presentation {
|
pub fn current(&self) -> Presentation {
|
||||||
self.current
|
self.current
|
||||||
}
|
}
|
||||||
@@ -87,6 +90,7 @@ impl Adaptive {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn deadline(&self) -> Option<Duration> {
|
pub fn deadline(&self) -> Option<Duration> {
|
||||||
self.pending
|
self.pending
|
||||||
.map(|(_, since)| since.saturating_add(HYSTERESIS))
|
.map(|(_, since)| since.saturating_add(HYSTERESIS))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub mod shape;
|
|||||||
|
|
||||||
pub use shape::Style as PanelStyle;
|
pub use shape::Style as PanelStyle;
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub const fn default_island_padding() -> f32 {
|
pub const fn default_island_padding() -> f32 {
|
||||||
4.0
|
4.0
|
||||||
}
|
}
|
||||||
@@ -75,6 +75,7 @@ impl Profile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn size(self, mode: Mode, collapsed_height: f64) -> [f64; 2] {
|
pub fn size(self, mode: Mode, collapsed_height: f64) -> [f64; 2] {
|
||||||
match mode {
|
match mode {
|
||||||
Mode::Idle => [self.idle_width, collapsed_height],
|
Mode::Idle => [self.idle_width, collapsed_height],
|
||||||
@@ -114,6 +115,7 @@ pub enum Mode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
|
#[must_use]
|
||||||
pub fn expanded(self) -> bool {
|
pub fn expanded(self) -> bool {
|
||||||
matches!(self, Self::Expanded(_))
|
matches!(self, Self::Expanded(_))
|
||||||
}
|
}
|
||||||
@@ -145,7 +147,7 @@ pub struct Hub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Hub {
|
impl Hub {
|
||||||
pub fn new(profile: Profile, collapsed_height: f64) -> Result<Self, &'static str> {
|
pub fn try_new(profile: Profile, collapsed_height: f64) -> Result<Self, &'static str> {
|
||||||
profile.validate()?;
|
profile.validate()?;
|
||||||
if !collapsed_height.is_finite() || !(24. ..=128.).contains(&collapsed_height) {
|
if !collapsed_height.is_finite() || !(24. ..=128.).contains(&collapsed_height) {
|
||||||
return Err("hub height must be 24..128");
|
return Err("hub height must be 24..128");
|
||||||
@@ -196,6 +198,7 @@ impl Hub {
|
|||||||
self.motion.retarget(target)
|
self.motion.retarget(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn mode(&self) -> Mode {
|
pub fn mode(&self) -> Mode {
|
||||||
self.mode
|
self.mode
|
||||||
}
|
}
|
||||||
@@ -301,10 +304,12 @@ impl Hub {
|
|||||||
self.active()
|
self.active()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn active(&self) -> bool {
|
pub fn active(&self) -> bool {
|
||||||
self.motion.active() || self.sample().previous_opacity > 0. || self.sample().opacity < 1.
|
self.motion.active() || self.sample().previous_opacity > 0. || self.sample().opacity < 1.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn sample(&self) -> Sample {
|
pub fn sample(&self) -> Sample {
|
||||||
let fade = |elapsed: Duration| (elapsed.as_secs_f32() / FADE.as_secs_f32()).clamp(0., 1.);
|
let fade = |elapsed: Duration| (elapsed.as_secs_f32() / FADE.as_secs_f32()).clamp(0., 1.);
|
||||||
let opacity = self.initial_opacity
|
let opacity = self.initial_opacity
|
||||||
@@ -324,11 +329,13 @@ impl Hub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn allocation_size(&self) -> [f64; 2] {
|
pub fn allocation_size(&self) -> [f64; 2] {
|
||||||
let maximum = self.motion.maximum_size();
|
let maximum = self.motion.maximum_size();
|
||||||
std::array::from_fn(|i| maximum[i].min(self.available[i]))
|
std::array::from_fn(|i| maximum[i].min(self.available[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn target_size(&self) -> [f64; 2] {
|
pub fn target_size(&self) -> [f64; 2] {
|
||||||
let target = self.motion.target().bounds;
|
let target = self.motion.target().bounds;
|
||||||
[target.width, target.height]
|
[target.width, target.height]
|
||||||
@@ -341,7 +348,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn workspace_width_changes_preserve_content_and_motion() {
|
fn workspace_width_changes_preserve_content_and_motion() {
|
||||||
let mut hub = Hub::new(Profile::default(), 34.).unwrap();
|
let mut hub = Hub::try_new(Profile::default(), 34.).unwrap();
|
||||||
hub.retarget_idle(270., [800., 600.]).unwrap();
|
hub.retarget_idle(270., [800., 600.]).unwrap();
|
||||||
hub.advance(Duration::from_millis(60), false);
|
hub.advance(Duration::from_millis(60), false);
|
||||||
let before = hub.sample();
|
let before = hub.sample();
|
||||||
@@ -358,7 +365,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn measured_height_preserves_fade_and_motion_and_caps_long_content() {
|
fn measured_height_preserves_fade_and_motion_and_caps_long_content() {
|
||||||
let mut hub = Hub::new(Profile::default(), 34.).unwrap();
|
let mut hub = Hub::try_new(Profile::default(), 34.).unwrap();
|
||||||
hub.retarget(Mode::Expanded(Content::Controls), [800., 600.])
|
hub.retarget(Mode::Expanded(Content::Controls), [800., 600.])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
hub.advance(Duration::from_millis(180), false);
|
hub.advance(Duration::from_millis(180), false);
|
||||||
@@ -382,7 +389,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_content_swap_fades_without_restarting_the_geometry() {
|
fn a_content_swap_fades_without_restarting_the_geometry() {
|
||||||
let mut hub = Hub::new(Profile::default(), 34.).unwrap();
|
let mut hub = Hub::try_new(Profile::default(), 34.).unwrap();
|
||||||
hub.retarget(Mode::Expanded(Content::Controls), [800., 600.])
|
hub.retarget(Mode::Expanded(Content::Controls), [800., 600.])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
hub.advance(Duration::from_secs(2), false);
|
hub.advance(Duration::from_secs(2), false);
|
||||||
@@ -404,7 +411,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn content_swap_uses_a_fixed_delay_instead_of_waiting_for_spring_settlement() {
|
fn content_swap_uses_a_fixed_delay_instead_of_waiting_for_spring_settlement() {
|
||||||
let mut hub = Hub::new(Profile::default(), 34.).unwrap();
|
let mut hub = Hub::try_new(Profile::default(), 34.).unwrap();
|
||||||
assert!(!hub.active());
|
assert!(!hub.active());
|
||||||
hub.retarget(Mode::Expanded(Content::Calendar), [800., 600.])
|
hub.retarget(Mode::Expanded(Content::Calendar), [800., 600.])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -430,7 +437,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn an_interrupted_content_delay_never_reveals_the_cancelled_panel() {
|
fn an_interrupted_content_delay_never_reveals_the_cancelled_panel() {
|
||||||
let mut hub = Hub::new(Profile::default(), 34.).unwrap();
|
let mut hub = Hub::try_new(Profile::default(), 34.).unwrap();
|
||||||
hub.retarget(Mode::Expanded(Content::Media), [800., 600.])
|
hub.retarget(Mode::Expanded(Content::Media), [800., 600.])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
hub.advance(Duration::from_millis(80), false);
|
hub.advance(Duration::from_millis(80), false);
|
||||||
@@ -448,7 +455,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn spring_overshoots_then_settles_within_its_reserved_surface() {
|
fn spring_overshoots_then_settles_within_its_reserved_surface() {
|
||||||
let mut hub = Hub::new(Profile::default(), 34.).unwrap();
|
let mut hub = Hub::try_new(Profile::default(), 34.).unwrap();
|
||||||
hub.retarget(Mode::Expanded(Content::Calendar), [800., 600.])
|
hub.retarget(Mode::Expanded(Content::Calendar), [800., 600.])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let allocation = hub.allocation_size();
|
let allocation = hub.allocation_size();
|
||||||
@@ -468,7 +475,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn overshoot_is_clipped_to_small_outputs_and_reduced_motion_removes_all_fades() {
|
fn overshoot_is_clipped_to_small_outputs_and_reduced_motion_removes_all_fades() {
|
||||||
let mut hub = Hub::new(Profile::default(), 34.).unwrap();
|
let mut hub = Hub::try_new(Profile::default(), 34.).unwrap();
|
||||||
hub.retarget(Mode::Expanded(Content::Calendar), [240., 180.])
|
hub.retarget(Mode::Expanded(Content::Calendar), [240., 180.])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
for _ in 0..60 {
|
for _ in 0..60 {
|
||||||
@@ -486,7 +493,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn interrupted_expansion_keeps_the_presented_shape() {
|
fn interrupted_expansion_keeps_the_presented_shape() {
|
||||||
let mut hub = Hub::new(Profile::default(), 34.).unwrap();
|
let mut hub = Hub::try_new(Profile::default(), 34.).unwrap();
|
||||||
hub.retarget(Mode::Expanded(Content::Media), [800., 600.])
|
hub.retarget(Mode::Expanded(Content::Media), [800., 600.])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
hub.advance(Duration::from_millis(40), false);
|
hub.advance(Duration::from_millis(40), false);
|
||||||
@@ -502,7 +509,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn narrow_outputs_and_reduced_motion_keep_the_hub_bounded() {
|
fn narrow_outputs_and_reduced_motion_keep_the_hub_bounded() {
|
||||||
let mut hub = Hub::new(Profile::default(), 30.).unwrap();
|
let mut hub = Hub::try_new(Profile::default(), 30.).unwrap();
|
||||||
hub.retarget(Mode::Expanded(Content::Calendar), [240., 180.])
|
hub.retarget(Mode::Expanded(Content::Calendar), [240., 180.])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
hub.advance(Duration::ZERO, true);
|
hub.advance(Duration::ZERO, true);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub struct Geometry {
|
|||||||
const COUNT: usize = 18;
|
const COUNT: usize = 18;
|
||||||
|
|
||||||
impl Geometry {
|
impl Geometry {
|
||||||
|
#[must_use]
|
||||||
pub fn rounded(bounds: Rect, radius: f64) -> Self {
|
pub fn rounded(bounds: Rect, radius: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bounds,
|
bounds,
|
||||||
@@ -75,7 +76,7 @@ pub struct Motion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Motion {
|
impl Motion {
|
||||||
pub fn new(geometry: Geometry) -> Result<Self, &'static str> {
|
pub fn try_new(geometry: Geometry) -> Result<Self, &'static str> {
|
||||||
Self::with_spring(
|
Self::with_spring(
|
||||||
geometry,
|
geometry,
|
||||||
SpringConfig {
|
SpringConfig {
|
||||||
@@ -132,10 +133,12 @@ impl Motion {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn current(&self) -> Geometry {
|
pub fn current(&self) -> Geometry {
|
||||||
Geometry::from_values(self.values.map(|value| value.current))
|
Geometry::from_values(self.values.map(|value| value.current))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn target(&self) -> Geometry {
|
pub fn target(&self) -> Geometry {
|
||||||
Geometry::from_values(self.values.map(|value| value.target))
|
Geometry::from_values(self.values.map(|value| value.target))
|
||||||
}
|
}
|
||||||
@@ -180,7 +183,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn interruption_keeps_the_current_geometry_and_velocity() {
|
fn interruption_keeps_the_current_geometry_and_velocity() {
|
||||||
let mut motion = Motion::new(collapsed()).unwrap();
|
let mut motion = Motion::try_new(collapsed()).unwrap();
|
||||||
motion.retarget(expanded()).unwrap();
|
motion.retarget(expanded()).unwrap();
|
||||||
motion.advance(Duration::from_millis(16), false);
|
motion.advance(Duration::from_millis(16), false);
|
||||||
let before = motion.current();
|
let before = motion.current();
|
||||||
@@ -197,7 +200,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dimensions_share_one_trajectory_and_stop_requesting_frames() {
|
fn dimensions_share_one_trajectory_and_stop_requesting_frames() {
|
||||||
let mut motion = Motion::new(collapsed()).unwrap();
|
let mut motion = Motion::try_new(collapsed()).unwrap();
|
||||||
motion.retarget(expanded()).unwrap();
|
motion.retarget(expanded()).unwrap();
|
||||||
for _ in 0..12 {
|
for _ in 0..12 {
|
||||||
motion.advance(Duration::from_millis(16), false);
|
motion.advance(Duration::from_millis(16), false);
|
||||||
@@ -218,7 +221,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reduced_motion_snaps_an_inflight_animation() {
|
fn reduced_motion_snaps_an_inflight_animation() {
|
||||||
let mut motion = Motion::new(collapsed()).unwrap();
|
let mut motion = Motion::try_new(collapsed()).unwrap();
|
||||||
motion.retarget(expanded()).unwrap();
|
motion.retarget(expanded()).unwrap();
|
||||||
motion.advance(Duration::from_millis(16), false);
|
motion.advance(Duration::from_millis(16), false);
|
||||||
assert!(!motion.advance(Duration::ZERO, true));
|
assert!(!motion.advance(Duration::ZERO, true));
|
||||||
@@ -263,7 +266,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_retarget_leaves_motion_untouched() {
|
fn invalid_retarget_leaves_motion_untouched() {
|
||||||
let mut motion = Motion::new(collapsed()).unwrap();
|
let mut motion = Motion::try_new(collapsed()).unwrap();
|
||||||
let mut invalid = expanded();
|
let mut invalid = expanded();
|
||||||
invalid.bounds.height = f64::NAN;
|
invalid.bounds.height = f64::NAN;
|
||||||
assert!(motion.retarget(invalid).is_err());
|
assert!(motion.retarget(invalid).is_err());
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ mod tests {
|
|||||||
zone: ZonePosition::Center,
|
zone: ZonePosition::Center,
|
||||||
bounds: Rect::new(70.3, 0., 160.4, 44.),
|
bounds: Rect::new(70.3, 0., 160.4, 44.),
|
||||||
}],
|
}],
|
||||||
PixelGrid::new(scale).unwrap(),
|
PixelGrid::try_new(scale).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let paths = notch_border(&scene, 0.1 / scale, edge).unwrap();
|
let paths = notch_border(&scene, 0.1 / scale, edge).unwrap();
|
||||||
@@ -325,7 +325,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
Rect::new(30.3, 0., width, height),
|
Rect::new(30.3, 0., width, height),
|
||||||
&[],
|
&[],
|
||||||
PixelGrid::new(scale).unwrap(),
|
PixelGrid::try_new(scale).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let body = scene.paint[0];
|
let body = scene.paint[0];
|
||||||
@@ -420,7 +420,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
Rect::new(0., 0., 96., 64.),
|
Rect::new(0., 0., 96., 64.),
|
||||||
&groups,
|
&groups,
|
||||||
PixelGrid::new(1.25).unwrap(),
|
PixelGrid::try_new(1.25).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let contours = contours(&scene, 0.1).unwrap();
|
let contours = contours(&scene, 0.1).unwrap();
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ pub enum Edge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Edge {
|
impl Edge {
|
||||||
|
#[must_use]
|
||||||
pub fn key(self) -> &'static str {
|
pub fn key(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Top => "top",
|
Self::Top => "top",
|
||||||
@@ -86,6 +87,7 @@ impl WorkspaceStyle {
|
|||||||
Self::AppIcons,
|
Self::AppIcons,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn key(self) -> &'static str {
|
pub fn key(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Numbers => "numbers",
|
Self::Numbers => "numbers",
|
||||||
@@ -96,6 +98,7 @@ impl WorkspaceStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn label(self) -> &'static str {
|
pub fn label(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Numbers => "Numbers",
|
Self::Numbers => "Numbers",
|
||||||
@@ -135,6 +138,7 @@ pub enum ItemKind {
|
|||||||
|
|
||||||
impl ItemKind {
|
impl ItemKind {
|
||||||
/// Smallest useful allocation before a flexible control moves into overflow.
|
/// Smallest useful allocation before a flexible control moves into overflow.
|
||||||
|
#[must_use]
|
||||||
pub fn minimum_width(self) -> Option<f32> {
|
pub fn minimum_width(self) -> Option<f32> {
|
||||||
match self {
|
match self {
|
||||||
Self::FocusedWindow => Some(48.),
|
Self::FocusedWindow => Some(48.),
|
||||||
@@ -144,6 +148,7 @@ impl ItemKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn label(self) -> &'static str {
|
pub fn label(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Overview => "Overview",
|
Self::Overview => "Overview",
|
||||||
@@ -163,6 +168,7 @@ impl ItemKind {
|
|||||||
Self::Overflow => "Overflow",
|
Self::Overflow => "Overflow",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[must_use]
|
||||||
pub fn representations(self) -> &'static [Representation] {
|
pub fn representations(self) -> &'static [Representation] {
|
||||||
use Representation::*;
|
use Representation::*;
|
||||||
match self {
|
match self {
|
||||||
@@ -265,6 +271,7 @@ impl<'de> Deserialize<'de> for Item {
|
|||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
/// Start at the configured preference and retain supported smaller forms.
|
/// Start at the configured preference and retain supported smaller forms.
|
||||||
|
#[must_use]
|
||||||
pub fn representations(&self) -> Vec<Representation> {
|
pub fn representations(&self) -> Vec<Representation> {
|
||||||
let supported = self.kind.representations();
|
let supported = self.kind.representations();
|
||||||
let preferred = self.representation.unwrap_or(supported[0]);
|
let preferred = self.representation.unwrap_or(supported[0]);
|
||||||
@@ -279,6 +286,7 @@ impl Item {
|
|||||||
alternatives
|
alternatives
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[must_use]
|
||||||
pub fn new(id: &str, kind: ItemKind) -> Self {
|
pub fn new(id: &str, kind: ItemKind) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: ItemId(id.into()),
|
id: ItemId(id.into()),
|
||||||
@@ -305,6 +313,7 @@ impl Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn available(&self, availability: Availability) -> bool {
|
pub fn available(&self, availability: Availability) -> bool {
|
||||||
if !self.visible {
|
if !self.visible {
|
||||||
return false;
|
return false;
|
||||||
@@ -351,6 +360,7 @@ pub enum GroupSurface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GroupSurface {
|
impl GroupSurface {
|
||||||
|
#[must_use]
|
||||||
pub fn key(self) -> &'static str {
|
pub fn key(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::None => "none",
|
Self::None => "none",
|
||||||
@@ -519,6 +529,7 @@ impl Default for Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Panel {
|
impl Panel {
|
||||||
|
#[must_use]
|
||||||
pub fn preset(&self) -> Option<PanelStyle> {
|
pub fn preset(&self) -> Option<PanelStyle> {
|
||||||
Some(self.style)
|
Some(self.style)
|
||||||
}
|
}
|
||||||
@@ -527,6 +538,7 @@ impl Panel {
|
|||||||
self.style = style;
|
self.style = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn surface(&self) -> PanelSurface {
|
pub fn surface(&self) -> PanelSurface {
|
||||||
if matches!(self.style, PanelStyle::Flat | PanelStyle::Frame) {
|
if matches!(self.style, PanelStyle::Flat | PanelStyle::Frame) {
|
||||||
PanelSurface::Solid
|
PanelSurface::Solid
|
||||||
@@ -535,6 +547,7 @@ impl Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn group_surface(&self) -> GroupSurface {
|
pub fn group_surface(&self) -> GroupSurface {
|
||||||
if self.surface() == PanelSurface::Solid {
|
if self.surface() == PanelSurface::Solid {
|
||||||
GroupSurface::None
|
GroupSurface::None
|
||||||
@@ -543,6 +556,7 @@ impl Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn zone_spacing(&self, zone: &Zone) -> f32 {
|
pub fn zone_spacing(&self, zone: &Zone) -> f32 {
|
||||||
if self.style.connected() {
|
if self.style.connected() {
|
||||||
0.
|
0.
|
||||||
@@ -551,6 +565,7 @@ impl Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn shape_parameters(&self) -> crate::scene::Parameters {
|
pub fn shape_parameters(&self) -> crate::scene::Parameters {
|
||||||
crate::scene::Parameters {
|
crate::scene::Parameters {
|
||||||
style: self.style,
|
style: self.style,
|
||||||
@@ -567,6 +582,7 @@ impl Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn resolved_geometry(&self) -> PanelGeometry {
|
pub fn resolved_geometry(&self) -> PanelGeometry {
|
||||||
let mut geometry = self.geometry.resolve(self.style);
|
let mut geometry = self.geometry.resolve(self.style);
|
||||||
if self.style == PanelStyle::Morph && self.morph.attached {
|
if self.style == PanelStyle::Morph && self.morph.attached {
|
||||||
@@ -575,6 +591,7 @@ impl Panel {
|
|||||||
geometry
|
geometry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn exclusive_zone(&self, hidden: bool) -> i32 {
|
pub fn exclusive_zone(&self, hidden: bool) -> i32 {
|
||||||
if hidden {
|
if hidden {
|
||||||
0
|
0
|
||||||
@@ -584,6 +601,7 @@ impl Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn group_count(&self) -> usize {
|
pub fn group_count(&self) -> usize {
|
||||||
[&self.start, &self.center, &self.end]
|
[&self.start, &self.center, &self.end]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -592,6 +610,7 @@ impl Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Appearance changes do not replace item ownership or measured allocation.
|
/// Appearance changes do not replace item ownership or measured allocation.
|
||||||
|
#[must_use]
|
||||||
pub fn same_composition(&self, other: &Self) -> bool {
|
pub fn same_composition(&self, other: &Self) -> bool {
|
||||||
let same_zone = |left: &Zone, right: &Zone| {
|
let same_zone = |left: &Zone, right: &Zone| {
|
||||||
left.spacing == right.spacing
|
left.spacing == right.spacing
|
||||||
@@ -615,12 +634,14 @@ impl Panel {
|
|||||||
&& same_zone(&self.end, &other.end)
|
&& same_zone(&self.end, &other.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn resolved_radius(&self, shell_radius: f32) -> CornerRadii {
|
pub fn resolved_radius(&self, shell_radius: f32) -> CornerRadii {
|
||||||
self.corner_radius
|
self.corner_radius
|
||||||
.unwrap_or_else(|| CornerRadii::uniform(shell_radius))
|
.unwrap_or_else(|| CornerRadii::uniform(shell_radius))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The overflow trigger is generated, never persisted as a configured item.
|
/// The overflow trigger is generated, never persisted as a configured item.
|
||||||
|
#[must_use]
|
||||||
pub fn overflow_group(&self) -> Group {
|
pub fn overflow_group(&self) -> Group {
|
||||||
Group {
|
Group {
|
||||||
id: GroupId("_overflow".into()),
|
id: GroupId("_overflow".into()),
|
||||||
@@ -646,6 +667,7 @@ impl Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn item(&self, id: &ItemId) -> Option<&Item> {
|
pub fn item(&self, id: &ItemId) -> Option<&Item> {
|
||||||
[&self.start, &self.center, &self.end]
|
[&self.start, &self.center, &self.end]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -655,6 +677,7 @@ impl Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build a stable initial composition; presets are an editor convenience.
|
/// Build a stable initial composition; presets are an editor convenience.
|
||||||
|
#[must_use]
|
||||||
pub fn from_preset(preset: PanelStyle) -> Self {
|
pub fn from_preset(preset: PanelStyle) -> Self {
|
||||||
use ItemKind::*;
|
use ItemKind::*;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub struct GeometryIntent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GeometryIntent {
|
impl GeometryIntent {
|
||||||
|
#[must_use]
|
||||||
pub fn resolve(self, style: crate::PanelStyle) -> PanelGeometry {
|
pub fn resolve(self, style: crate::PanelStyle) -> PanelGeometry {
|
||||||
use crate::PanelStyle::*;
|
use crate::PanelStyle::*;
|
||||||
let flush = matches!(style, Notch | Flat | Frame | CornerWrap);
|
let flush = matches!(style, Notch | Flat | Frame | CornerWrap);
|
||||||
|
|||||||
@@ -92,12 +92,14 @@ impl Resolution {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn fits(&self, available: f32) -> bool {
|
pub fn fits(&self, available: f32) -> bool {
|
||||||
self.required_width() <= available
|
self.required_width() <= available
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clamp a requested inset independently for each output. Content may still
|
/// Clamp a requested inset independently for each output. Content may still
|
||||||
/// overflow on an output too small to fit it even without an inset.
|
/// overflow on an output too small to fit it even without an inset.
|
||||||
|
#[must_use]
|
||||||
pub fn side_margin(&self, requested: i32, output_width: i32, padding: f32) -> i32 {
|
pub fn side_margin(&self, requested: i32, output_width: i32, padding: f32) -> i32 {
|
||||||
let maximum = ((output_width as f32 - 2. * padding - self.minimum_width) / 2.)
|
let maximum = ((output_width as f32 - 2. * padding - self.minimum_width) / 2.)
|
||||||
.floor()
|
.floor()
|
||||||
@@ -106,6 +108,7 @@ impl Resolution {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
panel: &Panel,
|
panel: &Panel,
|
||||||
measurements: &[Measurement],
|
measurements: &[Measurement],
|
||||||
@@ -116,6 +119,7 @@ pub fn resolve(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reserve separation between zones while keeping the center on the panel midpoint.
|
/// Reserve separation between zones while keeping the center on the panel midpoint.
|
||||||
|
#[must_use]
|
||||||
pub fn resolve_with_gap(
|
pub fn resolve_with_gap(
|
||||||
panel: &Panel,
|
panel: &Panel,
|
||||||
measurements: &[Measurement],
|
measurements: &[Measurement],
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||||||
pub struct CornerRadii(pub [f32; 4]);
|
pub struct CornerRadii(pub [f32; 4]);
|
||||||
|
|
||||||
impl CornerRadii {
|
impl CornerRadii {
|
||||||
|
#[must_use]
|
||||||
pub fn uniform(radius: f32) -> Self {
|
pub fn uniform(radius: f32) -> Self {
|
||||||
Self([radius; 4])
|
Self([radius; 4])
|
||||||
}
|
}
|
||||||
@@ -18,6 +19,7 @@ impl CornerRadii {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A panel touching the top edge must cover its top corners.
|
/// A panel touching the top edge must cover its top corners.
|
||||||
|
#[must_use]
|
||||||
pub fn at_top_edge(mut self, touching: bool) -> Self {
|
pub fn at_top_edge(mut self, touching: bool) -> Self {
|
||||||
if touching {
|
if touching {
|
||||||
self.0[0] = 0.;
|
self.0[0] = 0.;
|
||||||
@@ -26,6 +28,7 @@ impl CornerRadii {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn at_edge(mut self, edge: super::Edge, touching: bool) -> Self {
|
pub fn at_edge(mut self, edge: super::Edge, touching: bool) -> Self {
|
||||||
if touching {
|
if touching {
|
||||||
match edge {
|
match edge {
|
||||||
@@ -44,6 +47,7 @@ impl CornerRadii {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Keep every corner within half the shorter surface dimension.
|
/// Keep every corner within half the shorter surface dimension.
|
||||||
|
#[must_use]
|
||||||
pub fn clamped(self, width: f32, height: f32) -> Self {
|
pub fn clamped(self, width: f32, height: f32) -> Self {
|
||||||
let limit = width.min(height).max(0.) / 2.;
|
let limit = width.min(height).max(0.) / 2.;
|
||||||
Self(self.0.map(|value| value.min(limit)))
|
Self(self.0.map(|value| value.min(limit)))
|
||||||
@@ -52,6 +56,7 @@ impl CornerRadii {
|
|||||||
/// Scale all corners together when their sums exceed an adjacent edge.
|
/// Scale all corners together when their sums exceed an adjacent edge.
|
||||||
/// Unlike the symmetric round-box clamp, this permits a large free-side
|
/// Unlike the symmetric round-box clamp, this permits a large free-side
|
||||||
/// corner when the corner on the anchored side is square.
|
/// corner when the corner on the anchored side is square.
|
||||||
|
#[must_use]
|
||||||
pub fn normalized(self, width: f32, height: f32) -> Self {
|
pub fn normalized(self, width: f32, height: f32) -> Self {
|
||||||
let [tl, tr, br, bl] = self.0;
|
let [tl, tr, br, bl] = self.0;
|
||||||
let mut factor = 1.0_f64;
|
let mut factor = 1.0_f64;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub struct Primitive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive {
|
impl Primitive {
|
||||||
|
#[must_use]
|
||||||
pub fn packet(self) -> [f32; 10] {
|
pub fn packet(self) -> [f32; 10] {
|
||||||
let Rect {
|
let Rect {
|
||||||
x,
|
x,
|
||||||
@@ -66,6 +67,7 @@ impl Primitive {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn distance(self, point: [f64; 2]) -> f64 {
|
pub fn distance(self, point: [f64; 2]) -> f64 {
|
||||||
let rect = rectangle_distance(point, self.bounds);
|
let rect = rectangle_distance(point, self.bounds);
|
||||||
if self.operation >= 2 {
|
if self.operation >= 2 {
|
||||||
@@ -103,6 +105,7 @@ fn rectangle_distance(point: [f64; 2], b: Rect) -> f64 {
|
|||||||
x.max(0.).hypot(y.max(0.)) + x.max(y).min(0.)
|
x.max(0.).hypot(y.max(0.)) + x.max(y).min(0.)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn smooth_min(a: f64, b: f64, strength: f64) -> f64 {
|
pub fn smooth_min(a: f64, b: f64, strength: f64) -> f64 {
|
||||||
if strength <= 0. {
|
if strength <= 0. {
|
||||||
return a.min(b);
|
return a.min(b);
|
||||||
@@ -141,16 +144,19 @@ impl Scene {
|
|||||||
zones.into_iter().fold(f64::INFINITY, f64::min).max(-cutout)
|
zones.into_iter().fold(f64::INFINITY, f64::min).max(-cutout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn contains(&self, point: [f64; 2]) -> bool {
|
pub fn contains(&self, point: [f64; 2]) -> bool {
|
||||||
self.input.iter().any(|shape| shape.contains(point))
|
self.input.iter().any(|shape| shape.contains(point))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn packets(&self) -> Vec<[f32; 10]> {
|
pub fn packets(&self) -> Vec<[f32; 10]> {
|
||||||
self.paint.iter().map(|p| p.packet()).collect()
|
self.paint.iter().map(|p| p.packet()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Horizontal runs at physical-pixel centers. Corners and animated gaps
|
/// Horizontal runs at physical-pixel centers. Corners and animated gaps
|
||||||
/// stay click-through; adjacent equal runs are coalesced into one rectangle.
|
/// stay click-through; adjacent equal runs are coalesced into one rectangle.
|
||||||
|
#[must_use]
|
||||||
pub fn input_rectangles(&self, scale: f64) -> Vec<[i32; 4]> {
|
pub fn input_rectangles(&self, scale: f64) -> Vec<[i32; 4]> {
|
||||||
if !scale.is_finite() || scale <= 0. {
|
if !scale.is_finite() || scale <= 0. {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
@@ -246,7 +252,7 @@ fn add(
|
|||||||
input: bool,
|
input: bool,
|
||||||
zone: u8,
|
zone: u8,
|
||||||
) -> Result<(), &'static str> {
|
) -> Result<(), &'static str> {
|
||||||
let rounded = RoundedSurface::new(rect, radii, grid)?;
|
let rounded = RoundedSurface::try_new(rect, radii, grid)?;
|
||||||
if rounded.bounds().width == 0. || rounded.bounds().height == 0. {
|
if rounded.bounds().width == 0. || rounded.bounds().height == 0. {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -320,7 +326,7 @@ pub fn derive(
|
|||||||
operation: 1,
|
operation: 1,
|
||||||
blend_zone: 0,
|
blend_zone: 0,
|
||||||
});
|
});
|
||||||
scene.input.push(RoundedSurface::new(
|
scene.input.push(RoundedSurface::try_new(
|
||||||
Rect::new(bounds.x, bounds.y, bounds.width, top),
|
Rect::new(bounds.x, bounds.y, bounds.width, top),
|
||||||
CornerRadii::uniform(0.),
|
CornerRadii::uniform(0.),
|
||||||
grid,
|
grid,
|
||||||
@@ -583,8 +589,11 @@ pub fn derive(
|
|||||||
}
|
}
|
||||||
for input in &mut scene.input {
|
for input in &mut scene.input {
|
||||||
let [tl, tr, br, bl] = input.radii().0;
|
let [tl, tr, br, bl] = input.radii().0;
|
||||||
*input =
|
*input = RoundedSurface::try_new(
|
||||||
RoundedSurface::new(mirror(input.bounds()), CornerRadii([bl, br, tr, tl]), grid)?;
|
mirror(input.bounds()),
|
||||||
|
CornerRadii([bl, br, tr, tl]),
|
||||||
|
grid,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
for damage in &mut scene.damage {
|
for damage in &mut scene.damage {
|
||||||
*damage = mirror(*damage);
|
*damage = mirror(*damage);
|
||||||
@@ -624,7 +633,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
Rect::new(0., 0., 800., 36.),
|
Rect::new(0., 0., 800., 36.),
|
||||||
&groups(),
|
&groups(),
|
||||||
PixelGrid::new(1.25).unwrap(),
|
PixelGrid::try_new(1.25).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
@@ -677,7 +686,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
Rect::new(0., 0., 800., 600.),
|
Rect::new(0., 0., 800., 600.),
|
||||||
&[],
|
&[],
|
||||||
PixelGrid::new(1.25).unwrap(),
|
PixelGrid::try_new(1.25).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(scene.contains([18., 200.]));
|
assert!(scene.contains([18., 200.]));
|
||||||
@@ -714,7 +723,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
Rect::new(0., 0., 1600., 1000.),
|
Rect::new(0., 0., 1600., 1000.),
|
||||||
&[],
|
&[],
|
||||||
PixelGrid::new(1.5).unwrap(),
|
PixelGrid::try_new(1.5).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(scene.distance([800., 20.]) < 0. && scene.distance([800., 500.]) > 0.);
|
assert!(scene.distance([800., 20.]) < 0. && scene.distance([800., 500.]) > 0.);
|
||||||
@@ -746,7 +755,7 @@ mod tests {
|
|||||||
fn attached_styles_mirror_the_whole_outline_at_the_bottom() {
|
fn attached_styles_mirror_the_whole_outline_at_the_bottom() {
|
||||||
for style in [Style::Notch, Style::Frame, Style::CornerWrap] {
|
for style in [Style::Notch, Style::Frame, Style::CornerWrap] {
|
||||||
let bounds = Rect::new(0., 0., 800., if style == Style::Notch { 36. } else { 600. });
|
let bounds = Rect::new(0., 0., 800., if style == Style::Notch { 36. } else { 600. });
|
||||||
let grid = PixelGrid::new(1.).unwrap();
|
let grid = PixelGrid::try_new(1.).unwrap();
|
||||||
let top = derive(
|
let top = derive(
|
||||||
Parameters {
|
Parameters {
|
||||||
style,
|
style,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ impl Style {
|
|||||||
Self::WindowHug,
|
Self::WindowHug,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub const fn key(self) -> &'static str {
|
pub const fn key(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Floating => "floating",
|
Self::Floating => "floating",
|
||||||
@@ -57,6 +58,7 @@ impl Style {
|
|||||||
Self::WindowHug => "window-hug",
|
Self::WindowHug => "window-hug",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[must_use]
|
||||||
pub const fn label(self) -> &'static str {
|
pub const fn label(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Floating => "Floating",
|
Self::Floating => "Floating",
|
||||||
@@ -71,9 +73,11 @@ impl Style {
|
|||||||
Self::WindowHug => "Window hug",
|
Self::WindowHug => "Window hug",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[must_use]
|
||||||
pub const fn connected(self) -> bool {
|
pub const fn connected(self) -> bool {
|
||||||
matches!(self, Self::Capsules | Self::Notch)
|
matches!(self, Self::Capsules | Self::Notch)
|
||||||
}
|
}
|
||||||
|
#[must_use]
|
||||||
pub const fn detached(self) -> bool {
|
pub const fn detached(self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
@@ -100,6 +104,7 @@ pub enum ZonePosition {
|
|||||||
pub struct AnchoredRadii(pub [f32; 4]);
|
pub struct AnchoredRadii(pub [f32; 4]);
|
||||||
|
|
||||||
impl AnchoredRadii {
|
impl AnchoredRadii {
|
||||||
|
#[must_use]
|
||||||
pub fn screen_space(self, edge: Edge) -> CornerRadii {
|
pub fn screen_space(self, edge: Edge) -> CornerRadii {
|
||||||
let [flat_left, flat_right, free_right, free_left] = self.0;
|
let [flat_left, flat_right, free_right, free_left] = self.0;
|
||||||
CornerRadii(match edge {
|
CornerRadii(match edge {
|
||||||
@@ -173,13 +178,14 @@ pub struct PixelGrid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PixelGrid {
|
impl PixelGrid {
|
||||||
pub fn new(scale: f64) -> Result<Self, &'static str> {
|
pub fn try_new(scale: f64) -> Result<Self, &'static str> {
|
||||||
if !scale.is_finite() || scale <= 0. {
|
if !scale.is_finite() || scale <= 0. {
|
||||||
return Err("output scale must be finite and positive");
|
return Err("output scale must be finite and positive");
|
||||||
}
|
}
|
||||||
Ok(Self { scale })
|
Ok(Self { scale })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn snap(self, position: f64) -> f64 {
|
pub fn snap(self, position: f64) -> f64 {
|
||||||
(position * self.scale).round() / self.scale
|
(position * self.scale).round() / self.scale
|
||||||
}
|
}
|
||||||
@@ -218,7 +224,11 @@ pub struct RoundedSurface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RoundedSurface {
|
impl RoundedSurface {
|
||||||
pub fn new(bounds: Rect, radii: CornerRadii, grid: PixelGrid) -> Result<Self, &'static str> {
|
pub fn try_new(
|
||||||
|
bounds: Rect,
|
||||||
|
radii: CornerRadii,
|
||||||
|
grid: PixelGrid,
|
||||||
|
) -> Result<Self, &'static str> {
|
||||||
if radii.validate().is_err() {
|
if radii.validate().is_err() {
|
||||||
return Err("surface radii must be finite and nonnegative");
|
return Err("surface radii must be finite and nonnegative");
|
||||||
}
|
}
|
||||||
@@ -230,16 +240,19 @@ impl RoundedSurface {
|
|||||||
Ok(Self { bounds, radii })
|
Ok(Self { bounds, radii })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn bounds(self) -> Rect {
|
pub fn bounds(self) -> Rect {
|
||||||
self.bounds
|
self.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn radii(self) -> CornerRadii {
|
pub fn radii(self) -> CornerRadii {
|
||||||
self.radii
|
self.radii
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Right and bottom boundaries are excluded so a shared seam belongs to
|
/// Right and bottom boundaries are excluded so a shared seam belongs to
|
||||||
/// exactly one surface, rather than both neighbors.
|
/// exactly one surface, rather than both neighbors.
|
||||||
|
#[must_use]
|
||||||
pub fn contains(self, point: [f64; 2]) -> bool {
|
pub fn contains(self, point: [f64; 2]) -> bool {
|
||||||
let [x, y] = [point[0] - self.bounds.x, point[1] - self.bounds.y];
|
let [x, y] = [point[0] - self.bounds.x, point[1] - self.bounds.y];
|
||||||
let (width, height) = (self.bounds.width, self.bounds.height);
|
let (width, height) = (self.bounds.width, self.bounds.height);
|
||||||
@@ -339,7 +352,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn shared_boundaries_snap_without_seams_at_fractional_scales() {
|
fn shared_boundaries_snap_without_seams_at_fractional_scales() {
|
||||||
for scale in [1., 1.25, 1.5, 2.] {
|
for scale in [1., 1.25, 1.5, 2.] {
|
||||||
let grid = PixelGrid::new(scale).unwrap();
|
let grid = PixelGrid::try_new(scale).unwrap();
|
||||||
for origin in [-1800.3, -0.4, 0., 23.3] {
|
for origin in [-1800.3, -0.4, 0., 23.3] {
|
||||||
let left = grid.rect(Rect::new(origin, 0., 33.3, 36.)).unwrap();
|
let left = grid.rect(Rect::new(origin, 0., 33.3, 36.)).unwrap();
|
||||||
let right = grid.rect(Rect::new(origin + 33.3, 0., 42.7, 36.)).unwrap();
|
let right = grid.rect(Rect::new(origin + 33.3, 0., 42.7, 36.)).unwrap();
|
||||||
@@ -353,14 +366,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn floating_gaps_and_round_corners_do_not_capture_input() {
|
fn floating_gaps_and_round_corners_do_not_capture_input() {
|
||||||
let grid = PixelGrid::new(1.25).unwrap();
|
let grid = PixelGrid::try_new(1.25).unwrap();
|
||||||
let left = RoundedSurface::new(
|
let left = RoundedSurface::try_new(
|
||||||
Rect::new(8., 8., 100., 36.),
|
Rect::new(8., 8., 100., 36.),
|
||||||
CornerRadii::uniform(18.),
|
CornerRadii::uniform(18.),
|
||||||
grid,
|
grid,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let right = RoundedSurface::new(
|
let right = RoundedSurface::try_new(
|
||||||
Rect::new(116., 8., 100., 36.),
|
Rect::new(116., 8., 100., 36.),
|
||||||
CornerRadii::uniform(18.),
|
CornerRadii::uniform(18.),
|
||||||
grid,
|
grid,
|
||||||
@@ -381,15 +394,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn asymmetric_hit_testing_matches_a_vertically_mirrored_surface() {
|
fn asymmetric_hit_testing_matches_a_vertically_mirrored_surface() {
|
||||||
let grid = PixelGrid::new(1.).unwrap();
|
let grid = PixelGrid::try_new(1.).unwrap();
|
||||||
let bounds = Rect::new(0., 0., 100., 28.);
|
let bounds = Rect::new(0., 0., 100., 28.);
|
||||||
let top = RoundedSurface::new(
|
let top = RoundedSurface::try_new(
|
||||||
bounds,
|
bounds,
|
||||||
AnchoredRadii([0., 0., 20., 20.]).screen_space(Edge::Top),
|
AnchoredRadii([0., 0., 20., 20.]).screen_space(Edge::Top),
|
||||||
grid,
|
grid,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let bottom = RoundedSurface::new(
|
let bottom = RoundedSurface::try_new(
|
||||||
bounds,
|
bounds,
|
||||||
AnchoredRadii([0., 0., 20., 20.]).screen_space(Edge::Bottom),
|
AnchoredRadii([0., 0., 20., 20.]).screen_space(Edge::Bottom),
|
||||||
grid,
|
grid,
|
||||||
@@ -409,14 +422,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_connected_seam_has_exactly_one_input_owner() {
|
fn a_connected_seam_has_exactly_one_input_owner() {
|
||||||
let grid = PixelGrid::new(1.5).unwrap();
|
let grid = PixelGrid::try_new(1.5).unwrap();
|
||||||
let left = RoundedSurface::new(
|
let left = RoundedSurface::try_new(
|
||||||
Rect::new(0., 0., 30., 36.),
|
Rect::new(0., 0., 30., 36.),
|
||||||
CornerRadii([18., 0., 0., 18.]),
|
CornerRadii([18., 0., 0., 18.]),
|
||||||
grid,
|
grid,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let right = RoundedSurface::new(
|
let right = RoundedSurface::try_new(
|
||||||
Rect::new(30., 0., 40., 36.),
|
Rect::new(30., 0., 40., 36.),
|
||||||
CornerRadii([0., 18., 18., 0.]),
|
CornerRadii([0., 18., 18., 0.]),
|
||||||
grid,
|
grid,
|
||||||
@@ -431,9 +444,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn invalid_runtime_geometry_is_rejected() {
|
fn invalid_runtime_geometry_is_rejected() {
|
||||||
for scale in [0., -1., f64::NAN, f64::INFINITY] {
|
for scale in [0., -1., f64::NAN, f64::INFINITY] {
|
||||||
assert!(PixelGrid::new(scale).is_err());
|
assert!(PixelGrid::try_new(scale).is_err());
|
||||||
}
|
}
|
||||||
let grid = PixelGrid::new(1.).unwrap();
|
let grid = PixelGrid::try_new(1.).unwrap();
|
||||||
for bounds in [
|
for bounds in [
|
||||||
Rect::new(0., 0., -1., 28.),
|
Rect::new(0., 0., -1., 28.),
|
||||||
Rect::new(f64::NAN, 0., 10., 28.),
|
Rect::new(f64::NAN, 0., 10., 28.),
|
||||||
@@ -442,7 +455,7 @@ mod tests {
|
|||||||
assert!(grid.rect(bounds).is_err());
|
assert!(grid.rect(bounds).is_err());
|
||||||
}
|
}
|
||||||
assert!(
|
assert!(
|
||||||
RoundedSurface::new(Rect::new(0., 0., 10., 28.), CornerRadii::uniform(-1.), grid)
|
RoundedSurface::try_new(Rect::new(0., 0., 10., 28.), CornerRadii::uniform(-1.), grid)
|
||||||
.is_err()
|
.is_err()
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub fn current() -> Snapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Standalone appearance; it does not represent compositor acceptance.
|
/// Standalone appearance; it does not represent compositor acceptance.
|
||||||
|
#[must_use]
|
||||||
pub fn fallback() -> Snapshot {
|
pub fn fallback() -> Snapshot {
|
||||||
let theme = ferese_config::theme::default_theme();
|
let theme = ferese_config::theme::default_theme();
|
||||||
Snapshot {
|
Snapshot {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub fn rgba(s: &str) -> Result<[f64; 4], String> {
|
|||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn hex(c: [f64; 4]) -> String {
|
pub fn hex(c: [f64; 4]) -> String {
|
||||||
format!(
|
format!(
|
||||||
"#{:02X}{:02X}{:02X}{:02X}",
|
"#{:02X}{:02X}{:02X}{:02X}",
|
||||||
@@ -27,6 +28,7 @@ pub fn hex(c: [f64; 4]) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn luminance(c: [f64; 4]) -> f64 {
|
pub fn luminance(c: [f64; 4]) -> f64 {
|
||||||
let linear = |v: f64| {
|
let linear = |v: f64| {
|
||||||
if v <= 0.04045 {
|
if v <= 0.04045 {
|
||||||
@@ -38,11 +40,13 @@ pub fn luminance(c: [f64; 4]) -> f64 {
|
|||||||
0.2126 * linear(c[0]) + 0.7152 * linear(c[1]) + 0.0722 * linear(c[2])
|
0.2126 * linear(c[0]) + 0.7152 * linear(c[1]) + 0.0722 * linear(c[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn contrast(a: [f64; 4], b: [f64; 4]) -> f64 {
|
pub fn contrast(a: [f64; 4], b: [f64; 4]) -> f64 {
|
||||||
let (a, b) = (luminance(a), luminance(b));
|
let (a, b) = (luminance(a), luminance(b));
|
||||||
(a.max(b) + 0.05) / (a.min(b) + 0.05)
|
(a.max(b) + 0.05) / (a.min(b) + 0.05)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn composite(fg: [f64; 4], bg: [f64; 4]) -> [f64; 4] {
|
pub fn composite(fg: [f64; 4], bg: [f64; 4]) -> [f64; 4] {
|
||||||
let mut c = [0.; 4];
|
let mut c = [0.; 4];
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
@@ -52,10 +56,12 @@ pub fn composite(fg: [f64; 4], bg: [f64; 4]) -> [f64; 4] {
|
|||||||
c
|
c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn blend(a: [f64; 4], b: [f64; 4], p: f64) -> [f64; 4] {
|
pub fn blend(a: [f64; 4], b: [f64; 4], p: f64) -> [f64; 4] {
|
||||||
std::array::from_fn(|i| a[i] + (b[i] - a[i]) * p)
|
std::array::from_fn(|i| a[i] + (b[i] - a[i]) * p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn readable(preferred: [f64; 4], background: [f64; 4], minimum: f64) -> [f64; 4] {
|
pub fn readable(preferred: [f64; 4], background: [f64; 4], minimum: f64) -> [f64; 4] {
|
||||||
let mut preferred = preferred;
|
let mut preferred = preferred;
|
||||||
preferred[3] = 1.;
|
preferred[3] = 1.;
|
||||||
@@ -78,6 +84,7 @@ pub fn readable(preferred: [f64; 4], background: [f64; 4], minimum: f64) -> [f64
|
|||||||
target
|
target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn readable_across(
|
pub fn readable_across(
|
||||||
mut preferred: [f64; 4],
|
mut preferred: [f64; 4],
|
||||||
backgrounds: &[[f64; 4]],
|
backgrounds: &[[f64; 4]],
|
||||||
@@ -109,6 +116,7 @@ pub fn readable_across(
|
|||||||
target
|
target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn material_bounds(mut surface: [f64; 4], opacity: f64) -> [[f64; 4]; 2] {
|
pub fn material_bounds(mut surface: [f64; 4], opacity: f64) -> [[f64; 4]; 2] {
|
||||||
surface[3] = opacity;
|
surface[3] = opacity;
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ pub struct Family {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Family {
|
impl Family {
|
||||||
|
#[must_use]
|
||||||
pub fn palette(&self, appearance: Appearance) -> Option<&Palette> {
|
pub fn palette(&self, appearance: Appearance) -> Option<&Palette> {
|
||||||
match appearance {
|
match appearance {
|
||||||
Appearance::Light => self.light.as_ref(),
|
Appearance::Light => self.light.as_ref(),
|
||||||
@@ -41,6 +42,7 @@ impl Family {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn availability(&self) -> &'static str {
|
pub fn availability(&self) -> &'static str {
|
||||||
match (self.light.is_some(), self.dark.is_some()) {
|
match (self.light.is_some(), self.dark.is_some()) {
|
||||||
(true, true) => "light and dark",
|
(true, true) => "light and dark",
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ pub struct Paint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Paint {
|
impl Paint {
|
||||||
|
#[must_use]
|
||||||
pub fn effective_gradient(&self) -> Option<&Gradient> {
|
pub fn effective_gradient(&self) -> Option<&Gradient> {
|
||||||
if self.style == PaintStyle::Solid {
|
if self.style == PaintStyle::Solid {
|
||||||
None
|
None
|
||||||
@@ -153,6 +154,7 @@ pub struct ResolvedTheme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Accessibility takes precedence over a panel's authored opacity.
|
/// Accessibility takes precedence over a panel's authored opacity.
|
||||||
|
#[must_use]
|
||||||
pub fn effective_panel_opacity(
|
pub fn effective_panel_opacity(
|
||||||
inherited: f32,
|
inherited: f32,
|
||||||
custom: Option<f32>,
|
custom: Option<f32>,
|
||||||
@@ -166,6 +168,7 @@ pub fn effective_panel_opacity(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ResolvedTheme {
|
impl ResolvedTheme {
|
||||||
|
#[must_use]
|
||||||
pub fn panel_opacity(&self, custom: Option<f32>) -> f32 {
|
pub fn panel_opacity(&self, custom: Option<f32>) -> f32 {
|
||||||
let inherited = if self.tokens.material.style == "translucent" {
|
let inherited = if self.tokens.material.style == "translucent" {
|
||||||
self.tokens.material.opacity as f32
|
self.tokens.material.opacity as f32
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ pub(crate) fn accent_button_style(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Filled accent buttons and selected rows share this per-state contrast policy.
|
/// Filled accent buttons and selected rows share this per-state contrast policy.
|
||||||
|
#[must_use]
|
||||||
pub fn accent_button() -> theme::Button {
|
pub fn accent_button() -> theme::Button {
|
||||||
theme::Button::Custom {
|
theme::Button::Custom {
|
||||||
active: Box::new(|focused, theme| accent_button_style(theme, State::Active, focused)),
|
active: Box::new(|focused, theme| accent_button_style(theme, State::Active, focused)),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use cosmic::widget::{column, container, row};
|
|||||||
use cosmic::{Element, theme};
|
use cosmic::{Element, theme};
|
||||||
use jiff::Zoned;
|
use jiff::Zoned;
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn format_bar_time(now: &Zoned) -> String {
|
pub fn format_bar_time(now: &Zoned) -> String {
|
||||||
let months = [
|
let months = [
|
||||||
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sept", "oct", "nov", "dec",
|
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sept", "oct", "nov", "dec",
|
||||||
@@ -20,6 +21,7 @@ pub fn format_bar_time(now: &Zoned) -> String {
|
|||||||
if hour < 12 { "am" } else { "pm" }
|
if hour < 12 { "am" } else { "pm" }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
#[must_use]
|
||||||
pub fn month(today: jiff::civil::Date, offset: i32) -> (jiff::civil::Date, usize, usize) {
|
pub fn month(today: jiff::civil::Date, offset: i32) -> (jiff::civil::Date, usize, usize) {
|
||||||
let month_index = i32::from(today.year()) * 12 + i32::from(today.month()) - 1 + offset;
|
let month_index = i32::from(today.year()) * 12 + i32::from(today.month()) - 1 + offset;
|
||||||
let year = month_index.div_euclid(12) as i16;
|
let year = month_index.div_euclid(12) as i16;
|
||||||
@@ -30,6 +32,7 @@ pub fn month(today: jiff::civil::Date, offset: i32) -> (jiff::civil::Date, usize
|
|||||||
(first, start, days)
|
(first, start, days)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn grid<'a, M: Clone + 'a>(
|
pub fn grid<'a, M: Clone + 'a>(
|
||||||
today: jiff::civil::Date,
|
today: jiff::civil::Date,
|
||||||
offset: i32,
|
offset: i32,
|
||||||
@@ -41,6 +44,7 @@ pub fn grid<'a, M: Clone + 'a>(
|
|||||||
grid_with_density(today, offset, palette, font, opacity, navigation, false)
|
grid_with_density(today, offset, palette, font, opacity, navigation, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn compact_grid<'a, M: Clone + 'a>(
|
pub fn compact_grid<'a, M: Clone + 'a>(
|
||||||
today: jiff::civil::Date,
|
today: jiff::civil::Date,
|
||||||
offset: i32,
|
offset: i32,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use cosmic::iced::Color;
|
use cosmic::iced::Color;
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn luminance(color: Color) -> f32 {
|
pub fn luminance(color: Color) -> f32 {
|
||||||
let linear = |v: f32| {
|
let linear = |v: f32| {
|
||||||
if v <= 0.04045 {
|
if v <= 0.04045 {
|
||||||
@@ -11,6 +12,7 @@ pub fn luminance(color: Color) -> f32 {
|
|||||||
0.2126 * linear(color.r) + 0.7152 * linear(color.g) + 0.0722 * linear(color.b)
|
0.2126 * linear(color.r) + 0.7152 * linear(color.g) + 0.0722 * linear(color.b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn contrast(a: Color, b: Color) -> f32 {
|
pub fn contrast(a: Color, b: Color) -> f32 {
|
||||||
let (a, b) = (luminance(a), luminance(b));
|
let (a, b) = (luminance(a), luminance(b));
|
||||||
(a.max(b) + 0.05) / (a.min(b) + 0.05)
|
(a.max(b) + 0.05) / (a.min(b) + 0.05)
|
||||||
@@ -19,6 +21,7 @@ pub fn contrast(a: Color, b: Color) -> f32 {
|
|||||||
/// Resolve a control fill against its surface before selecting its foreground.
|
/// Resolve a control fill against its surface before selecting its foreground.
|
||||||
/// The returned fill is opaque, so wallpaper and translucent panels cannot change
|
/// The returned fill is opaque, so wallpaper and translucent panels cannot change
|
||||||
/// the contrast of a small, filled control.
|
/// the contrast of a small, filled control.
|
||||||
|
#[must_use]
|
||||||
pub fn composite(fill: Color, surface: Color) -> Color {
|
pub fn composite(fill: Color, surface: Color) -> Color {
|
||||||
let a = fill.a.clamp(0., 1.);
|
let a = fill.a.clamp(0., 1.);
|
||||||
Color::from_rgb(
|
Color::from_rgb(
|
||||||
@@ -30,6 +33,7 @@ pub fn composite(fill: Color, surface: Color) -> Color {
|
|||||||
|
|
||||||
/// Preserve the user's preferred color when it passes normal-text contrast.
|
/// Preserve the user's preferred color when it passes normal-text contrast.
|
||||||
/// Otherwise black or white always provides at least 4.5:1 on an opaque sRGB fill.
|
/// Otherwise black or white always provides at least 4.5:1 on an opaque sRGB fill.
|
||||||
|
#[must_use]
|
||||||
pub fn foreground(background: Color, preferred: Color) -> Color {
|
pub fn foreground(background: Color, preferred: Color) -> Color {
|
||||||
let preferred = composite(preferred, background);
|
let preferred = composite(preferred, background);
|
||||||
if contrast(background, preferred) >= 4.5 {
|
if contrast(background, preferred) >= 4.5 {
|
||||||
@@ -44,6 +48,7 @@ pub fn foreground(background: Color, preferred: Color) -> Color {
|
|||||||
|
|
||||||
/// Prefer the theme's foreground for filled controls by adjusting the accent's
|
/// Prefer the theme's foreground for filled controls by adjusting the accent's
|
||||||
/// brightness until the text has enough contrast.
|
/// brightness until the text has enough contrast.
|
||||||
|
#[must_use]
|
||||||
pub fn accent_pair(background: Color, preferred: Color) -> (Color, Color) {
|
pub fn accent_pair(background: Color, preferred: Color) -> (Color, Color) {
|
||||||
let on = composite(preferred, background);
|
let on = composite(preferred, background);
|
||||||
let shade = if luminance(on) > luminance(background) {
|
let shade = if luminance(on) > luminance(background) {
|
||||||
@@ -67,6 +72,7 @@ pub fn accent_pair(background: Color, preferred: Color) -> (Color, Color) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve configured accent transparency before passing an opaque color to a toolkit.
|
/// Resolve configured accent transparency before passing an opaque color to a toolkit.
|
||||||
|
#[must_use]
|
||||||
pub fn accent_color(accent: Color, surface: Color) -> cosmic::cosmic_theme::palette::Srgb {
|
pub fn accent_color(accent: Color, surface: Color) -> cosmic::cosmic_theme::palette::Srgb {
|
||||||
rgba(composite(accent, surface)).color
|
rgba(composite(accent, surface)).color
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,23 +5,28 @@ use cosmic::{theme, widget};
|
|||||||
|
|
||||||
use crate::{Palette, mix};
|
use crate::{Palette, mix};
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn surface(background: Color, radius: f32) -> theme::Container<'static> {
|
pub fn surface(background: Color, radius: f32) -> theme::Container<'static> {
|
||||||
theme::Container::custom(move |_| surface_appearance(background, radius))
|
theme::Container::custom(move |_| surface_appearance(background, radius))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn button_style(p: Palette, selected: bool) -> theme::Button {
|
pub fn button_style(p: Palette, selected: bool) -> theme::Button {
|
||||||
button_style_with_focus(p, selected, true)
|
button_style_with_focus(p, selected, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn button_style_with_focus(p: Palette, selected: bool, focus_visible: bool) -> theme::Button {
|
pub fn button_style_with_focus(p: Palette, selected: bool, focus_visible: bool) -> theme::Button {
|
||||||
styled_button(p, selected, false, 1.0, focus_visible)
|
styled_button(p, selected, false, 1.0, focus_visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Selection cards share native button states while revealing the modal material.
|
/// Selection cards share native button states while revealing the modal material.
|
||||||
|
#[must_use]
|
||||||
pub fn material_button_style(p: Palette, selected: bool, material_opacity: f32) -> theme::Button {
|
pub fn material_button_style(p: Palette, selected: bool, material_opacity: f32) -> theme::Button {
|
||||||
material_button_style_with_focus(p, selected, material_opacity, true)
|
material_button_style_with_focus(p, selected, material_opacity, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn material_button_style_with_focus(
|
pub fn material_button_style_with_focus(
|
||||||
p: Palette,
|
p: Palette,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
@@ -101,10 +106,12 @@ pub(crate) fn switch_colors(palette: Palette, enabled: bool, hovered: bool) -> (
|
|||||||
(track, thumb)
|
(track, thumb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn navigation_style(p: Palette, selected: bool) -> theme::Button {
|
pub fn navigation_style(p: Palette, selected: bool) -> theme::Button {
|
||||||
styled_button(p, selected, true, 1.0, true)
|
styled_button(p, selected, true, 1.0, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn settings_input(p: Palette) -> theme::TextInput {
|
pub fn settings_input(p: Palette) -> theme::TextInput {
|
||||||
let appearance = move |focused: bool, hovered: bool| cosmic::widget::text_input::Appearance {
|
let appearance = move |focused: bool, hovered: bool| cosmic::widget::text_input::Appearance {
|
||||||
shape: Some(BorderShape::Continuous),
|
shape: Some(BorderShape::Continuous),
|
||||||
@@ -229,6 +236,7 @@ fn styled_button(
|
|||||||
|
|
||||||
/// Authentication actions use normal text while enabled and muted text while disabled.
|
/// Authentication actions use normal text while enabled and muted text while disabled.
|
||||||
/// Keep the disabled fill neutral so contrast correction cannot make it look active.
|
/// Keep the disabled fill neutral so contrast correction cannot make it look active.
|
||||||
|
#[must_use]
|
||||||
pub fn authentication_button(palette: Palette) -> theme::Button {
|
pub fn authentication_button(palette: Palette) -> theme::Button {
|
||||||
let paint = move |enabled: bool, interaction: f32, focused: bool| {
|
let paint = move |enabled: bool, interaction: f32, focused: bool| {
|
||||||
let (fill, foreground) = if enabled {
|
let (fill, foreground) = if enabled {
|
||||||
@@ -260,6 +268,7 @@ pub fn authentication_button(palette: Palette) -> theme::Button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn authentication_input(palette: Palette) -> theme::TextInput {
|
pub fn authentication_input(palette: Palette) -> theme::TextInput {
|
||||||
let text = palette.text;
|
let text = palette.text;
|
||||||
let muted = palette.muted;
|
let muted = palette.muted;
|
||||||
@@ -293,6 +302,7 @@ pub fn authentication_input(palette: Palette) -> theme::TextInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn lock_input(
|
pub fn lock_input(
|
||||||
radius: f32,
|
radius: f32,
|
||||||
accent: cosmic::iced::Color,
|
accent: cosmic::iced::Color,
|
||||||
@@ -361,6 +371,7 @@ pub fn shell_button(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn surface_appearance(background: Color, radius: f32) -> container::Style {
|
pub fn surface_appearance(background: Color, radius: f32) -> container::Style {
|
||||||
container::Style {
|
container::Style {
|
||||||
background: Some(Background::Color(background)),
|
background: Some(Background::Color(background)),
|
||||||
@@ -373,6 +384,7 @@ pub fn surface_appearance(background: Color, radius: f32) -> container::Style {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn notification_button(
|
pub fn notification_button(
|
||||||
foreground: Color,
|
foreground: Color,
|
||||||
hover: Color,
|
hover: Color,
|
||||||
@@ -396,6 +408,7 @@ pub fn notification_button(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn filled_button(fill: Color, foreground: Color, radius: f32, opacity: f32) -> theme::Button {
|
pub fn filled_button(fill: Color, foreground: Color, radius: f32, opacity: f32) -> theme::Button {
|
||||||
let paint = move |outline| button::Style {
|
let paint = move |outline| button::Style {
|
||||||
shape: Some(BorderShape::Continuous),
|
shape: Some(BorderShape::Continuous),
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ fn desktop(p: &Palette) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn svg(
|
pub fn svg(
|
||||||
family: &Family,
|
family: &Family,
|
||||||
variant: Option<Appearance>,
|
variant: Option<Appearance>,
|
||||||
@@ -274,6 +275,7 @@ fn svg_with_radius(
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn neighbor(index: usize, count: usize, key: &keyboard::key::Named) -> Option<usize> {
|
pub fn neighbor(index: usize, count: usize, key: &keyboard::key::Named) -> Option<usize> {
|
||||||
use keyboard::key::Named::*;
|
use keyboard::key::Named::*;
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/// Radius of a circular inset. Squircle insets retain an `Outline` and its distance offset.
|
/// Radius of a circular inset. Squircle insets retain an `Outline` and its distance offset.
|
||||||
|
#[must_use]
|
||||||
pub fn inner_radius(outer: f32, padding: f32) -> f32 {
|
pub fn inner_radius(outer: f32, padding: f32) -> f32 {
|
||||||
(outer - padding).max(0.0)
|
(outer - padding).max(0.0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ pub fn accented(source: &'static [u8], size: u16, foreground: Color, accent: Col
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn tint_svg(source: &[u8], foreground: Color, accent: Color) -> Vec<u8> {
|
pub fn tint_svg(source: &[u8], foreground: Color, accent: Color) -> Vec<u8> {
|
||||||
let rgba = |foreground: Color| {
|
let rgba = |foreground: Color| {
|
||||||
format!(
|
format!(
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ pub fn heading<'a, M: 'a>(
|
|||||||
.align_y(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn badge<'a, M: 'a>(
|
pub fn badge<'a, M: 'a>(
|
||||||
content: Element<'a, M>,
|
content: Element<'a, M>,
|
||||||
accent: Color,
|
accent: Color,
|
||||||
@@ -55,6 +56,7 @@ pub fn badge<'a, M: 'a>(
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn section<'a, M: 'a>(
|
pub fn section<'a, M: 'a>(
|
||||||
content: Element<'a, M>,
|
content: Element<'a, M>,
|
||||||
foreground: Color,
|
foreground: Color,
|
||||||
@@ -84,6 +86,7 @@ pub fn section_label<'a>(
|
|||||||
.class(cosmic::theme::Text::Color(muted))
|
.class(cosmic::theme::Text::Color(muted))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn separator<'a, M: 'a>(foreground: Color, opacity: f32) -> Element<'a, M> {
|
pub fn separator<'a, M: 'a>(foreground: Color, opacity: f32) -> Element<'a, M> {
|
||||||
container(cosmic::widget::Space::new().width(Length::Fill).height(1))
|
container(cosmic::widget::Space::new().width(Length::Fill).height(1))
|
||||||
.class(crate::controls::surface(
|
.class(crate::controls::surface(
|
||||||
@@ -93,6 +96,7 @@ pub fn separator<'a, M: 'a>(foreground: Color, opacity: f32) -> Element<'a, M> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn switch<'a, M: 'a>(enabled: bool, palette: crate::Palette, opacity: f32) -> Element<'a, M> {
|
pub fn switch<'a, M: 'a>(enabled: bool, palette: crate::Palette, opacity: f32) -> Element<'a, M> {
|
||||||
let (track, thumb) = crate::controls::switch_colors(palette, enabled, false);
|
let (track, thumb) = crate::controls::switch_colors(palette, enabled, false);
|
||||||
let track_radius = 10.;
|
let track_radius = 10.;
|
||||||
@@ -123,6 +127,7 @@ pub fn switch<'a, M: 'a>(enabled: bool, palette: crate::Palette, opacity: f32) -
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn slider(foreground: Color, opacity: f32) -> cosmic::theme::iced::Slider {
|
pub fn slider(foreground: Color, opacity: f32) -> cosmic::theme::iced::Slider {
|
||||||
let style = std::rc::Rc::new(move |_: &cosmic::Theme| {
|
let style = std::rc::Rc::new(move |_: &cosmic::Theme| {
|
||||||
use cosmic::iced::widget::slider::{Breakpoint, Handle, HandleShape, Rail, Style};
|
use cosmic::iced::widget::slider::{Breakpoint, Handle, HandleShape, Rail, Style};
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ pub struct Palette {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Palette {
|
impl Palette {
|
||||||
|
#[must_use]
|
||||||
pub fn from_resolved(theme: &ferese_theme_model::ResolvedTheme) -> Self {
|
pub fn from_resolved(theme: &ferese_theme_model::ResolvedTheme) -> Self {
|
||||||
let color = |value: &str| parse_color(value).expect("validated resolved theme color");
|
let color = |value: &str| parse_color(value).expect("validated resolved theme color");
|
||||||
Self {
|
Self {
|
||||||
@@ -50,6 +51,7 @@ impl Palette {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn flat(mut self) -> Self {
|
pub fn flat(mut self) -> Self {
|
||||||
self.background = self.sidebar;
|
self.background = self.sidebar;
|
||||||
self.card = self.sidebar;
|
self.card = self.sidebar;
|
||||||
@@ -138,6 +140,7 @@ impl Palette {
|
|||||||
cosmic::Theme::custom(std::sync::Arc::new(native)).corner_shape(BorderShape::Continuous)
|
cosmic::Theme::custom(std::sync::Arc::new(native)).corner_shape(BorderShape::Continuous)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn application_style(self, background: Color) -> cosmic::iced::theme::Style {
|
pub fn application_style(self, background: Color) -> cosmic::iced::theme::Style {
|
||||||
cosmic::iced::theme::Style {
|
cosmic::iced::theme::Style {
|
||||||
background_color: background,
|
background_color: background,
|
||||||
@@ -147,6 +150,7 @@ impl Palette {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn parse_color(value: &str) -> Option<Color> {
|
pub fn parse_color(value: &str) -> Option<Color> {
|
||||||
let hex = value.strip_prefix('#')?;
|
let hex = value.strip_prefix('#')?;
|
||||||
let packed = u32::from_str_radix(hex, 16).ok()?;
|
let packed = u32::from_str_radix(hex, 16).ok()?;
|
||||||
@@ -163,6 +167,7 @@ pub fn parse_color(value: &str) -> Option<Color> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn mix(a: Color, b: Color, t: f32) -> Color {
|
pub fn mix(a: Color, b: Color, t: f32) -> Color {
|
||||||
Color::from_rgb(
|
Color::from_rgb(
|
||||||
a.r + (b.r - a.r) * t,
|
a.r + (b.r - a.r) * t,
|
||||||
@@ -171,6 +176,7 @@ pub fn mix(a: Color, b: Color, t: f32) -> Color {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn surface_shade(base: Color) -> Color {
|
pub fn surface_shade(base: Color) -> Color {
|
||||||
if crate::luminance(base) > 0.5 {
|
if crate::luminance(base) > 0.5 {
|
||||||
Color::BLACK
|
Color::BLACK
|
||||||
@@ -179,6 +185,7 @@ pub fn surface_shade(base: Color) -> Color {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn material_opacity(theme: &ferese_theme_model::ResolvedTheme) -> f32 {
|
pub fn material_opacity(theme: &ferese_theme_model::ResolvedTheme) -> f32 {
|
||||||
if theme.tokens.material.style == "translucent" {
|
if theme.tokens.material.style == "translucent" {
|
||||||
theme.tokens.material.opacity as f32
|
theme.tokens.material.opacity as f32
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ impl<M> Surface<'_, M> {
|
|||||||
&mut collector,
|
&mut collector,
|
||||||
);
|
);
|
||||||
let size = layout.bounds().size();
|
let size = layout.bounds().size();
|
||||||
let scene = PixelGrid::new(self.options.scale)
|
let scene = PixelGrid::try_new(self.options.scale)
|
||||||
.and_then(|grid| {
|
.and_then(|grid| {
|
||||||
ferese_panel::scene::derive(
|
ferese_panel::scene::derive(
|
||||||
self.options.parameters,
|
self.options.parameters,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub struct Workspace<'a> {
|
|||||||
pub visible_elsewhere: bool,
|
pub visible_elsewhere: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn width(style: WorkspaceStyle) -> f32 {
|
pub fn width(style: WorkspaceStyle) -> f32 {
|
||||||
match style {
|
match style {
|
||||||
WorkspaceStyle::Numbers | WorkspaceStyle::Dots => 24.,
|
WorkspaceStyle::Numbers | WorkspaceStyle::Dots => 24.,
|
||||||
@@ -25,6 +26,7 @@ pub fn width(style: WorkspaceStyle) -> f32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn tooltip_label(workspace: &Workspace<'_>) -> String {
|
pub fn tooltip_label(workspace: &Workspace<'_>) -> String {
|
||||||
let name = if workspace.name.is_empty() || workspace.name == workspace.index.to_string() {
|
let name = if workspace.name.is_empty() || workspace.name == workspace.index.to_string() {
|
||||||
String::new()
|
String::new()
|
||||||
@@ -44,6 +46,7 @@ pub fn tooltip_label(workspace: &Workspace<'_>) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn indicator<'a, M: 'a>(
|
pub fn indicator<'a, M: 'a>(
|
||||||
style: WorkspaceStyle,
|
style: WorkspaceStyle,
|
||||||
workspace: Workspace<'a>,
|
workspace: Workspace<'a>,
|
||||||
@@ -193,6 +196,7 @@ pub fn indicator<'a, M: 'a>(
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn sample<'a, M: 'a>(
|
pub fn sample<'a, M: 'a>(
|
||||||
style: WorkspaceStyle,
|
style: WorkspaceStyle,
|
||||||
palette: Palette,
|
palette: Palette,
|
||||||
|
|||||||
@@ -87,15 +87,14 @@ pub enum ResultKind {
|
|||||||
Fail,
|
Fail,
|
||||||
Cancelled,
|
Cancelled,
|
||||||
}
|
}
|
||||||
fn handle(value: &Option<String>) -> Result<Option<Id>, Box<dyn Error>> {
|
fn handle(value: Option<&str>) -> Result<Option<Id>, Box<dyn Error>> {
|
||||||
value
|
value
|
||||||
.as_ref()
|
|
||||||
.map(|value| serde_json::from_str(value).map_err(Into::into))
|
.map(|value| serde_json::from_str(value).map_err(Into::into))
|
||||||
.transpose()
|
.transpose()
|
||||||
}
|
}
|
||||||
fn value(
|
fn value(
|
||||||
progress: Option<f32>,
|
progress: Option<f32>,
|
||||||
message: &Option<String>,
|
message: Option<&str>,
|
||||||
remaining_ms: Option<u64>,
|
remaining_ms: Option<u64>,
|
||||||
) -> Result<Option<Value>, Box<dyn Error>> {
|
) -> Result<Option<Value>, Box<dyn Error>> {
|
||||||
if usize::from(progress.is_some())
|
if usize::from(progress.is_some())
|
||||||
@@ -107,7 +106,7 @@ fn value(
|
|||||||
}
|
}
|
||||||
Ok(progress
|
Ok(progress
|
||||||
.map(Value::Progress)
|
.map(Value::Progress)
|
||||||
.or_else(|| message.clone().map(Value::Text))
|
.or_else(|| message.map(|message| Value::Text(message.to_owned())))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
remaining_ms.map(|remaining_ms| Value::Countdown {
|
remaining_ms.map(|remaining_ms| Value::Countdown {
|
||||||
remaining_ms,
|
remaining_ms,
|
||||||
@@ -156,7 +155,8 @@ pub fn command(action: &Action, socket: &Path, json_output: bool) -> Result<(),
|
|||||||
priority,
|
priority,
|
||||||
lease_ms,
|
lease_ms,
|
||||||
} => {
|
} => {
|
||||||
let value = value(*progress, message, *remaining_ms)?.unwrap_or(Value::Indeterminate);
|
let value = value(*progress, message.as_deref(), *remaining_ms)?
|
||||||
|
.unwrap_or(Value::Indeterminate);
|
||||||
let data = Data {
|
let data = Data {
|
||||||
source: if let Some(source) = source {
|
source: if let Some(source) = source {
|
||||||
serde_json::from_value(json!(source))?
|
serde_json::from_value(json!(source))?
|
||||||
@@ -196,8 +196,8 @@ pub fn command(action: &Action, socket: &Path, json_output: bool) -> Result<(),
|
|||||||
handle: token,
|
handle: token,
|
||||||
} => Publish::Update {
|
} => Publish::Update {
|
||||||
local: local.clone(),
|
local: local.clone(),
|
||||||
handle: handle(token)?,
|
handle: handle(token.as_deref())?,
|
||||||
value: value(*progress, message, *remaining_ms)?,
|
value: value(*progress, message.as_deref(), *remaining_ms)?,
|
||||||
lease_ms: *lease_ms,
|
lease_ms: *lease_ms,
|
||||||
},
|
},
|
||||||
Action::End {
|
Action::End {
|
||||||
@@ -207,7 +207,7 @@ pub fn command(action: &Action, socket: &Path, json_output: bool) -> Result<(),
|
|||||||
handle: token,
|
handle: token,
|
||||||
} => Publish::End {
|
} => Publish::End {
|
||||||
local: local.clone(),
|
local: local.clone(),
|
||||||
handle: handle(token)?,
|
handle: handle(token.as_deref())?,
|
||||||
result: match result {
|
result: match result {
|
||||||
ResultKind::Ok => State::Done,
|
ResultKind::Ok => State::Done,
|
||||||
ResultKind::Fail => State::Failed,
|
ResultKind::Fail => State::Failed,
|
||||||
@@ -220,7 +220,7 @@ pub fn command(action: &Action, socket: &Path, json_output: bool) -> Result<(),
|
|||||||
handle: token,
|
handle: token,
|
||||||
} => Publish::Cancel {
|
} => Publish::Cancel {
|
||||||
local: local.clone(),
|
local: local.clone(),
|
||||||
handle: handle(token)?,
|
handle: handle(token.as_deref())?,
|
||||||
},
|
},
|
||||||
Action::Hook {
|
Action::Hook {
|
||||||
local,
|
local,
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ pub(super) struct Runtime {
|
|||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
pub(super) fn new(panel: &Panel) -> Self {
|
pub(super) fn new(panel: &Panel) -> Self {
|
||||||
let mut hub = Hub::new(panel.morph, panel.resolved_geometry().height.into())
|
let mut hub = Hub::try_new(panel.morph, panel.resolved_geometry().height.into())
|
||||||
.expect("validated panel hub");
|
.expect("validated panel hub");
|
||||||
hub.pin_radii(panel.corner_radius.map(|r| r.0.map(f64::from)))
|
hub.pin_radii(panel.corner_radius.map(|r| r.0.map(f64::from)))
|
||||||
.expect("validated panel radii");
|
.expect("validated panel radii");
|
||||||
@@ -3539,7 +3539,7 @@ mod tests {
|
|||||||
bounds.height,
|
bounds.height,
|
||||||
),
|
),
|
||||||
&[],
|
&[],
|
||||||
ferese_panel::shape::PixelGrid::new(1.5).unwrap(),
|
ferese_panel::shape::PixelGrid::try_new(1.5).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(scene.paint.len(), 3);
|
assert_eq!(scene.paint.len(), 3);
|
||||||
|
|||||||
Reference in new issue
Block a user