feat: size label while selecting a region

This commit is contained in:
elwrcl committed 2026-10-03 02:32:25 +03:00
1 parent e0b5d0e9f5
commit bd58d2c1b7
2 files changed
+140 -2

No files matched your search

+29 -2
View File
@@ -50,6 +50,8 @@ pub struct Overlay {
anchor: Option<usize>,
/// Shift, Alt and Space as the keyboard last reported them.
mods: Mods,
/// The surface the pointer is on and its surface-local position.
pointer: Option<(usize, (f64, f64))>,
outcome: Option<Option<Choice>>,
}
@@ -112,6 +114,7 @@ pub fn select(wl: &mut Wayland, outputs: &[Output], frames: &[Frame]) -> Result<
selection: Selection::default(),
anchor: None,
mods: Mods::default(),
pointer: None,
outcome: None,
});
let result = wl.dispatch_blocking(|s| s.overlay.as_ref().is_some_and(|o| o.outcome.is_some()));
@@ -143,7 +146,12 @@ impl Surface {
})
}
fn draw(&mut self, sel: Option<PixelRect>, qh: &QueueHandle<State>) {
fn draw(
&mut self,
sel: Option<PixelRect>,
label: Option<(PixelRect, (f64, f64))>,
qh: &QueueHandle<State>,
) {
let stride = self.width as i32 * 4;
let Ok((buffer, canvas)) = self.pool.create_buffer(
self.width as i32,
@@ -155,6 +163,22 @@ impl Surface {
return;
};
render::compose(canvas, &self.dark, self.bright.as_raw(), self.width, sel);
if let Some((rect, (px, py))) = label {
let scale = self.width as f64 / self.geom.width as f64;
let text = render::size_text(rect);
let size = (
((crate::toolbar::draw::measure(&text) + 12.0) as f64 * scale).ceil() as u32,
((crate::toolbar::draw::LABEL * 1.2 + 12.0) as f64 * scale).ceil() as u32,
);
let pill = crate::toolbar::draw::pill(size, scale as f32, &text);
let at = render::label_origin(
(px * scale, py * scale),
(pill.width(), pill.height()),
(self.width, self.height),
16.0 * scale,
);
render::blend(canvas, self.width, &pill, at);
}
let surface = self.layer.wl_surface();
surface.damage_buffer(0, 0, self.width as i32, self.height as i32);
@@ -223,10 +247,12 @@ impl Overlay {
fn redraw(&mut self, i: usize, qh: &QueueHandle<State>) {
let sel = self.selection_on(i);
// The size label goes next to the pointer, on the output it is on.
let label = sel.zip(self.pointer.filter(|(on, _)| *on == i).map(|(_, at)| at));
let s = &mut self.surfaces[i];
s.dirty = true;
if s.configured && !s.frame_pending {
s.draw(sel, qh);
s.draw(sel, label, qh);
}
}
@@ -268,6 +294,7 @@ impl Overlay {
let Some(i) = self.index_of(&event.surface) else {
continue;
};
self.pointer = Some((i, event.position));
let g = &self.surfaces[i].geom;
let p = Point {
x: g.x as f64 + event.position.0,
+111
View File
@@ -36,6 +36,58 @@ pub fn compose(dst: &mut [u8], dark: &[u8], bright: &[u8], width: u32, sel: Opti
}
}
/// Where a `label`-sized box goes next to the pointer: `gap` px right of and
/// below it, or on the other side where it would leave the surface.
pub fn label_origin(
pointer: (f64, f64),
label: (u32, u32),
surface: (u32, u32),
gap: f64,
) -> (u32, u32) {
let place = |p: f64, len: u32, max: u32| {
let after = p + gap;
let v = if after + len as f64 <= max as f64 {
after
} else {
p - gap - len as f64
};
v.clamp(0.0, max.saturating_sub(len) as f64) as u32
};
(
place(pointer.0, label.0, surface.0),
place(pointer.1, label.1, surface.1),
)
}
/// `W × H` of a selection in physical pixels.
pub fn size_text(r: crate::frame::PixelRect) -> String {
format!("{} × {}", r.width, r.height)
}
/// Draws premultiplied `src` over the XRGB8888 `dst` at `at`, clipped.
pub fn blend(dst: &mut [u8], dst_width: u32, src: &tiny_skia::Pixmap, at: (u32, u32)) {
let dst_height = (dst.len() / 4) as u32 / dst_width.max(1);
for sy in 0..src.height() {
let y = at.1 + sy;
if y >= dst_height {
break;
}
for sx in 0..src.width() {
let x = at.0 + sx;
if x >= dst_width {
break;
}
let s = src.pixel(sx, sy).expect("in bounds");
let i = ((y * dst_width + x) * 4) as usize;
let inv = 255 - s.alpha() as u16;
let over = |d: u8, c: u8| (c as u16 + (d as u16 * inv + 127) / 255) as u8;
dst[i] = over(dst[i], s.blue());
dst[i + 1] = over(dst[i + 1], s.green());
dst[i + 2] = over(dst[i + 2], s.red());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -96,4 +148,63 @@ mod tests {
compose(&mut dst, &dark, &[2u8; 16], 2, Some(sel));
assert_eq!(px(&dst, 2, 1, 1), [255; 4]);
}
#[test]
fn label_sits_below_right_of_the_pointer() {
assert_eq!(
label_origin((100.0, 100.0), (80, 24), (1920, 1080), 16.0),
(116, 116)
);
}
#[test]
fn label_flips_at_edges() {
assert_eq!(
label_origin((1900.0, 100.0), (80, 24), (1920, 1080), 16.0),
(1900 - 16 - 80, 116)
);
assert_eq!(
label_origin((100.0, 1070.0), (80, 24), (1920, 1080), 16.0),
(116, 1070 - 16 - 24)
);
assert_eq!(
label_origin((1900.0, 1070.0), (80, 24), (1920, 1080), 16.0),
(1804, 1030)
);
// Tiny outputs: never negative.
assert_eq!(label_origin((5.0, 5.0), (80, 24), (60, 20), 16.0), (0, 0));
}
#[test]
fn size_text_uses_the_multiplication_sign() {
let r = PixelRect {
x: 3,
y: 4,
width: 640,
height: 480,
};
assert_eq!(size_text(r), "640 × 480");
}
#[test]
fn blend_draws_over_and_clips() {
let (w, h) = (4u32, 2u32);
let mut dst = vec![100u8; (w * h * 4) as usize];
let mut src = tiny_skia::Pixmap::new(2, 2).unwrap();
src.fill(tiny_skia::Color::from_rgba8(255, 0, 0, 255));
blend(&mut dst, w, &src, (3, 0));
assert_eq!(px(&dst, w, 3, 0)[..3], [0, 0, 255], "BGR red, opaque");
assert_eq!(
px(&dst, w, 2, 0)[..3],
[100, 100, 100],
"left of it untouched"
);
let mut half = tiny_skia::Pixmap::new(1, 1).unwrap();
half.fill(tiny_skia::Color::from_rgba8(0, 0, 0, 128));
blend(&mut dst, w, &half, (0, 1));
assert!(
(px(&dst, w, 0, 1)[0] as i32 - 50).abs() <= 1,
"half black over 100"
);
}
}