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>,
|
anchor: Option<usize>,
|
||||||
/// Shift, Alt and Space as the keyboard last reported them.
|
/// Shift, Alt and Space as the keyboard last reported them.
|
||||||
mods: Mods,
|
mods: Mods,
|
||||||
|
/// The surface the pointer is on and its surface-local position.
|
||||||
|
pointer: Option<(usize, (f64, f64))>,
|
||||||
outcome: Option<Option<Choice>>,
|
outcome: Option<Option<Choice>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +114,7 @@ pub fn select(wl: &mut Wayland, outputs: &[Output], frames: &[Frame]) -> Result<
|
|||||||
selection: Selection::default(),
|
selection: Selection::default(),
|
||||||
anchor: None,
|
anchor: None,
|
||||||
mods: Mods::default(),
|
mods: Mods::default(),
|
||||||
|
pointer: None,
|
||||||
outcome: None,
|
outcome: None,
|
||||||
});
|
});
|
||||||
let result = wl.dispatch_blocking(|s| s.overlay.as_ref().is_some_and(|o| o.outcome.is_some()));
|
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 stride = self.width as i32 * 4;
|
||||||
let Ok((buffer, canvas)) = self.pool.create_buffer(
|
let Ok((buffer, canvas)) = self.pool.create_buffer(
|
||||||
self.width as i32,
|
self.width as i32,
|
||||||
@@ -155,6 +163,22 @@ impl Surface {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
render::compose(canvas, &self.dark, self.bright.as_raw(), self.width, sel);
|
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();
|
let surface = self.layer.wl_surface();
|
||||||
surface.damage_buffer(0, 0, self.width as i32, self.height as i32);
|
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>) {
|
fn redraw(&mut self, i: usize, qh: &QueueHandle<State>) {
|
||||||
let sel = self.selection_on(i);
|
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];
|
let s = &mut self.surfaces[i];
|
||||||
s.dirty = true;
|
s.dirty = true;
|
||||||
if s.configured && !s.frame_pending {
|
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 {
|
let Some(i) = self.index_of(&event.surface) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
self.pointer = Some((i, event.position));
|
||||||
let g = &self.surfaces[i].geom;
|
let g = &self.surfaces[i].geom;
|
||||||
let p = Point {
|
let p = Point {
|
||||||
x: g.x as f64 + event.position.0,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -96,4 +148,63 @@ mod tests {
|
|||||||
compose(&mut dst, &dark, &[2u8; 16], 2, Some(sel));
|
compose(&mut dst, &dark, &[2u8; 16], 2, Some(sel));
|
||||||
assert_eq!(px(&dst, 2, 1, 1), [255; 4]);
|
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