feat: size label while selecting a region
This commit is contained in:
1 parent
e0b5d0e9f5
commit
bd58d2c1b7
2 files changed
+140
-2
No files matched your search
+29
-2
@@ -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
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user