Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions src/backend/wayland/toolbar/events.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::draw::Color;
use crate::ui::toolbar::model::ToolbarSliderSpec;

/// Kinds of hit regions and their drag semantics.
#[derive(Clone, Debug, PartialEq)]
Expand Down Expand Up @@ -49,19 +48,6 @@ impl HitKind {
}
}

/// Convert normalized drag position [0,1] to a delay in seconds.
pub fn delay_secs_from_t(t: f64) -> f64 {
let spec = ToolbarSliderSpec::DELAY_SECONDS;
spec.min + t.clamp(0.0, 1.0) * (spec.max - spec.min)
}

/// Convert a delay in ms to normalized [0,1] position for sliders.
pub fn delay_t_from_ms(delay_ms: u64) -> f64 {
let spec = ToolbarSliderSpec::DELAY_SECONDS;
let delay_s = (delay_ms as f64 / 1000.0).clamp(spec.min, spec.max);
(delay_s - spec.min) / (spec.max - spec.min)
}

/// Convert HSV to RGB for color picker math.
pub fn hsv_to_rgb(h: f64, s: f64, v: f64) -> Color {
let h = (h - h.floor()).clamp(0.0, 1.0) * 6.0;
Expand Down
293 changes: 212 additions & 81 deletions src/backend/wayland/toolbar/hit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::backend::wayland::state::{color_log, debug_toolbar_color_logging_enab
use crate::backend::wayland::toolbar::events::HitKind;
use crate::backend::wayland::toolbar_intent::ToolbarIntent;
use crate::ui::toolbar::ToolbarEvent;
use crate::ui::toolbar::model::ToolbarSliderSpec;
use crate::ui::toolbar::model::{ToolbarSlider, ToolbarSliderSpec, ToolbarSliderTarget};

#[derive(Clone, Debug)]
pub struct HitRegion {
Expand Down Expand Up @@ -43,22 +43,32 @@ pub fn intent_for_hit(hit: &HitRegion, x: f64, y: f64) -> Option<(ToolbarIntent,
use crate::backend::wayland::toolbar::events::HitKind::*;
use crate::ui::toolbar::ToolbarEvent;
let event = match hit.kind {
DragSetThickness { min, max } => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
let value = min + t * (max - min);
ToolbarEvent::SetThickness(value)
}
DragSetMarkerOpacity { min, max } => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
let value = min + t * (max - min);
ToolbarEvent::SetMarkerOpacity(value)
}
DragSetFontSize => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
let spec = ToolbarSliderSpec::FONT_SIZE;
let value = spec.min + t * (spec.max - spec.min);
ToolbarEvent::SetFontSize(value)
}
DragSetThickness { min, max } => slider_event_for_hit(
ToolbarSliderTarget::Thickness,
ToolbarSliderSpec {
min,
max,
step: ToolbarSliderSpec::THICKNESS.step,
},
hit,
x,
),
DragSetMarkerOpacity { min, max } => slider_event_for_hit(
ToolbarSliderTarget::MarkerOpacity,
ToolbarSliderSpec {
min,
max,
step: ToolbarSliderSpec::MARKER_OPACITY.step,
},
hit,
x,
),
DragSetFontSize => slider_event_for_hit(
ToolbarSliderTarget::FontSize,
ToolbarSliderSpec::FONT_SIZE,
hit,
x,
),
PickColor { x: px, y: py, w, h } => {
let hue = ((x - px) / w).clamp(0.0, 1.0);
let value = (1.0 - (y - py) / h).clamp(0.0, 1.0);
Expand All @@ -71,30 +81,30 @@ pub fn intent_for_hit(hit: &HitRegion, x: f64, y: f64) -> Option<(ToolbarIntent,
}
ToolbarEvent::SetColor(color)
}
DragUndoDelay => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
ToolbarEvent::SetUndoDelay(crate::backend::wayland::toolbar::events::delay_secs_from_t(
t,
))
}
DragRedoDelay => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
ToolbarEvent::SetRedoDelay(crate::backend::wayland::toolbar::events::delay_secs_from_t(
t,
))
}
DragCustomUndoDelay => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
ToolbarEvent::SetCustomUndoDelay(
crate::backend::wayland::toolbar::events::delay_secs_from_t(t),
)
}
DragCustomRedoDelay => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
ToolbarEvent::SetCustomRedoDelay(
crate::backend::wayland::toolbar::events::delay_secs_from_t(t),
)
}
DragUndoDelay => slider_event_for_hit(
ToolbarSliderTarget::UndoDelay,
ToolbarSliderSpec::DELAY_SECONDS,
hit,
x,
),
DragRedoDelay => slider_event_for_hit(
ToolbarSliderTarget::RedoDelay,
ToolbarSliderSpec::DELAY_SECONDS,
hit,
x,
),
DragCustomUndoDelay => slider_event_for_hit(
ToolbarSliderTarget::CustomUndoDelay,
ToolbarSliderSpec::DELAY_SECONDS,
hit,
x,
),
DragCustomRedoDelay => slider_event_for_hit(
ToolbarSliderTarget::CustomRedoDelay,
ToolbarSliderSpec::DELAY_SECONDS,
hit,
x,
),
DragMoveTop => ToolbarEvent::MoveTopToolbar { x, y },
DragMoveSide => ToolbarEvent::MoveSideToolbar { x, y },
crate::backend::wayland::toolbar::events::HitKind::Click => hit.event.clone(),
Expand All @@ -111,22 +121,32 @@ pub fn drag_intent_for_hit(hit: &HitRegion, x: f64, y: f64) -> Option<ToolbarInt
use crate::backend::wayland::toolbar::events::HitKind::*;
use crate::ui::toolbar::ToolbarEvent;
match hit.kind {
DragSetThickness { min, max } => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
let value = min + t * (max - min);
Some(ToolbarIntent(ToolbarEvent::SetThickness(value)))
}
DragSetMarkerOpacity { min, max } => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
let value = min + t * (max - min);
Some(ToolbarIntent(ToolbarEvent::SetMarkerOpacity(value)))
}
DragSetFontSize => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
let spec = ToolbarSliderSpec::FONT_SIZE;
let value = spec.min + t * (spec.max - spec.min);
Some(ToolbarIntent(ToolbarEvent::SetFontSize(value)))
}
DragSetThickness { min, max } => Some(ToolbarIntent(slider_event_for_hit(
ToolbarSliderTarget::Thickness,
ToolbarSliderSpec {
min,
max,
step: ToolbarSliderSpec::THICKNESS.step,
},
hit,
x,
))),
DragSetMarkerOpacity { min, max } => Some(ToolbarIntent(slider_event_for_hit(
ToolbarSliderTarget::MarkerOpacity,
ToolbarSliderSpec {
min,
max,
step: ToolbarSliderSpec::MARKER_OPACITY.step,
},
hit,
x,
))),
DragSetFontSize => Some(ToolbarIntent(slider_event_for_hit(
ToolbarSliderTarget::FontSize,
ToolbarSliderSpec::FONT_SIZE,
hit,
x,
))),
PickColor { x: px, y: py, w, h } => {
let hue = ((x - px) / w).clamp(0.0, 1.0);
let value = (1.0 - (y - py) / h).clamp(0.0, 1.0);
Expand All @@ -139,36 +159,50 @@ pub fn drag_intent_for_hit(hit: &HitRegion, x: f64, y: f64) -> Option<ToolbarInt
}
Some(ToolbarIntent(ToolbarEvent::SetColor(color)))
}
DragUndoDelay => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
Some(ToolbarIntent(ToolbarEvent::SetUndoDelay(
crate::backend::wayland::toolbar::events::delay_secs_from_t(t),
)))
}
DragRedoDelay => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
Some(ToolbarIntent(ToolbarEvent::SetRedoDelay(
crate::backend::wayland::toolbar::events::delay_secs_from_t(t),
)))
}
DragCustomUndoDelay => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
Some(ToolbarIntent(ToolbarEvent::SetCustomUndoDelay(
crate::backend::wayland::toolbar::events::delay_secs_from_t(t),
)))
}
DragCustomRedoDelay => {
let t = ((x - hit.rect.0) / hit.rect.2).clamp(0.0, 1.0);
Some(ToolbarIntent(ToolbarEvent::SetCustomRedoDelay(
crate::backend::wayland::toolbar::events::delay_secs_from_t(t),
)))
}
DragUndoDelay => Some(ToolbarIntent(slider_event_for_hit(
ToolbarSliderTarget::UndoDelay,
ToolbarSliderSpec::DELAY_SECONDS,
hit,
x,
))),
DragRedoDelay => Some(ToolbarIntent(slider_event_for_hit(
ToolbarSliderTarget::RedoDelay,
ToolbarSliderSpec::DELAY_SECONDS,
hit,
x,
))),
DragCustomUndoDelay => Some(ToolbarIntent(slider_event_for_hit(
ToolbarSliderTarget::CustomUndoDelay,
ToolbarSliderSpec::DELAY_SECONDS,
hit,
x,
))),
DragCustomRedoDelay => Some(ToolbarIntent(slider_event_for_hit(
ToolbarSliderTarget::CustomRedoDelay,
ToolbarSliderSpec::DELAY_SECONDS,
hit,
x,
))),
DragMoveTop => Some(ToolbarIntent(ToolbarEvent::MoveTopToolbar { x, y })),
DragMoveSide => Some(ToolbarIntent(ToolbarEvent::MoveSideToolbar { x, y })),
_ => None,
}
}

fn slider_event_for_hit(
target: ToolbarSliderTarget,
spec: ToolbarSliderSpec,
hit: &HitRegion,
pointer_x: f64,
) -> ToolbarEvent {
ToolbarSlider {
target,
spec,
value: spec.min,
}
.event_for_pointer_x(pointer_x, hit.rect.0, hit.rect.2)
}

fn focusable_indices(hits: &[HitRegion]) -> Vec<usize> {
hits.iter()
.enumerate()
Expand Down Expand Up @@ -206,3 +240,100 @@ pub fn focused_event(hits: &[HitRegion], focus: Option<usize>) -> Option<Toolbar
.and_then(|idx| hits.get(idx))
.map(|hit| hit.event.clone())
}

#[cfg(test)]
mod tests {
use super::*;

fn click(event: ToolbarEvent) -> HitRegion {
HitRegion {
rect: (10.0, 20.0, 30.0, 40.0),
event,
kind: HitKind::Click,
tooltip: None,
}
}

fn thickness_slider() -> HitRegion {
HitRegion {
rect: (100.0, 0.0, 200.0, 20.0),
event: ToolbarEvent::SetThickness(1.0),
kind: HitKind::DragSetThickness {
min: 10.0,
max: 20.0,
},
tooltip: None,
}
}

fn assert_set_thickness(event: ToolbarEvent, expected: f64) {
match event {
ToolbarEvent::SetThickness(value) => {
assert!(
(value - expected).abs() < 0.000_001,
"expected {expected}, got {value}"
);
}
other => panic!("unexpected event: {other:?}"),
}
}

#[test]
fn slider_press_and_drag_use_same_pointer_mapping() {
let hit = thickness_slider();

let (press, start_drag) = intent_for_hit(&hit, 200.0, 10.0).expect("press intent");
let drag = drag_intent_for_hit(&hit, 200.0, 10.0).expect("drag intent");

assert!(start_drag);
assert_set_thickness(press.0, 15.0);
assert_set_thickness(drag.0, 15.0);
}

#[test]
fn slider_pointer_mapping_clamps_to_hit_rect() {
let hit = thickness_slider();

let (left, _) = intent_for_hit(&hit, 100.0, 10.0).expect("left intent");
let (right, _) = intent_for_hit(&hit, 300.0, 10.0).expect("right intent");

assert_set_thickness(left.0, 10.0);
assert_set_thickness(right.0, 20.0);
assert!(intent_for_hit(&hit, 99.0, 10.0).is_none());
assert!(drag_intent_for_hit(&hit, 301.0, 10.0).is_none());
}

#[test]
fn focus_traversal_uses_click_hits_only() {
let hits = vec![
click(ToolbarEvent::Undo),
thickness_slider(),
click(ToolbarEvent::Redo),
];

assert_eq!(next_focus_index(&hits, None, false), Some(0));
assert_eq!(next_focus_index(&hits, Some(0), false), Some(2));
assert_eq!(next_focus_index(&hits, Some(2), false), Some(0));
assert_eq!(next_focus_index(&hits, None, true), Some(2));
assert_eq!(next_focus_index(&hits, Some(2), true), Some(0));
}

#[test]
fn focused_event_returns_the_focused_hit_event() {
let hits = vec![
click(ToolbarEvent::Undo),
thickness_slider(),
click(ToolbarEvent::Redo),
];

assert!(matches!(
focused_event(&hits, Some(0)),
Some(ToolbarEvent::Undo)
));
assert!(matches!(
focused_event(&hits, Some(2)),
Some(ToolbarEvent::Redo)
));
assert!(focused_event(&hits, None).is_none());
}
}
3 changes: 2 additions & 1 deletion src/backend/wayland/toolbar/layout/side/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ mod presets;
mod settings;
mod sliders;

pub(super) use super::super::events::{HitKind, delay_secs_from_t, delay_t_from_ms};
pub(super) use super::super::events::HitKind;
pub(super) use super::super::format_binding_label;
pub(super) use super::super::hit::HitRegion;
pub(super) use super::spec::ToolbarLayoutSpec;
pub(super) use crate::ui::toolbar::model::{delay_secs_from_t, delay_t_from_ms};
pub(super) use crate::ui::toolbar::snapshot::ToolContext;
pub(super) use crate::ui::toolbar::{ToolbarEvent, ToolbarSnapshot};

Expand Down
2 changes: 1 addition & 1 deletion src/backend/wayland/toolbar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod rows;
pub mod surfaces;

#[allow(unused_imports)]
pub use events::{HitKind, ToolbarCursorHint, delay_secs_from_t, delay_t_from_ms, hsv_to_rgb};
pub use events::{HitKind, ToolbarCursorHint, hsv_to_rgb};
#[allow(unused_imports)]
pub use layout::{build_side_hits, build_top_hits, side_size, top_size};
pub use main::*;
Expand Down
Loading
Loading