input, render: keep fast touch typing whole, fire pattern nodes once, and make the software image path cheap
Two ways a tap was lost on a phone. The gesture machine serves one finger: a second finger landing while the first was still down went to the auxiliary slot and never reached it, so with two thumbs every overlapping tap on a keypad vanished. A finger landing on a primary that is still a plain press (GestureState::is_plain_press: no drag, scroll, swipe or slider in flight) now commits that press as a tap at its last position and takes the primary slot; the earlier finger's release reports through on_touch_up, and a second finger during a pattern stroke or a slider drag stays out as before. The touch-up body moves to release_primary so both paths share it. The other loss needed one finger: the 8 px travel that turns a press into a scroll, swipe or drag, and the 6 px stray that cancels a touch long-press, were compared against physical coordinates, so at buffer scale 2 they were 4 and 3 logical px and an ordinary thumb tap became a vertical drag whose release skipped the button. SwipeConfig carries the surface's scale and the machine reads the thresholds through slop() and stray(), which are logical. Pattern grids fired on_node on every motion event within a node's snap radius, and the Slider move outcome requested a repaint each time, so drawing a pattern rebuilt and re-rastered the surface at the touchscreen's motion rate. The machine remembers the node under the finger (last_pattern_node) and emits only on change; the handlers split pattern_node_at from pattern_msg, with pattern_node_msg kept on top of them; a move outcome without a message no longer requests a redraw. SoftwareCanvas::draw_image_data premultiplied the whole source in floating point on every call, so a full-surface backdrop paid a pass over every pixel per frame before the blit. Premultiplied copies now live in a 32 MiB least-recently-drawn cache keyed by size, content fingerprint and alpha, the key the GLES texture cache already used; fingerprint_rgba moves to render::helpers and both backends share it. The entry records whether the source was fully opaque, and an opaque image drawn 1:1 with no clip and full alpha is copied row by row into the canvas instead of going through tiny-skia's bilinear pipeline. docs/architecture.md describes the primary-slot hand-over, docs/widgets.md the once-per-node contract, and the CHANGELOG lists the five fixes.
This commit is contained in:
@@ -24,11 +24,9 @@
|
||||
//! single texture larger than the whole budget still draws — the cache
|
||||
//! then holds that one entry until something replaces it.
|
||||
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{ Hash, Hasher };
|
||||
|
||||
use glow::HasContext;
|
||||
|
||||
use crate::render::helpers::fingerprint_rgba;
|
||||
use crate::types::Rect;
|
||||
|
||||
use super::helpers::{ ortho_rect, upload_rgba_texture };
|
||||
@@ -40,37 +38,6 @@ use super::GlesCanvas;
|
||||
/// the compositor.
|
||||
pub const IMAGE_CACHE_MAX_BYTES: usize = 32 * 1024 * 1024;
|
||||
|
||||
/// Compute a 64-bit fingerprint of an RGBA buffer for the texture
|
||||
/// cache. Hashes the full byte slice for small buffers (icons,
|
||||
/// thumbnails — below 16 KB ≈ 64×64 RGBA), and falls back to a
|
||||
/// strided 8 × 512-byte sample for anything larger so a wallpaper
|
||||
/// blit does not pay an 8 MB hash on every frame. Both modes
|
||||
/// distinguish the chevron-icon-style cases that motivated the move
|
||||
/// to content-keying — the SVG-rasterised buffers differ across
|
||||
/// most of their interior bytes, not just at the corners.
|
||||
fn fingerprint_rgba( bytes: &[u8] ) -> u64
|
||||
{
|
||||
const FULL_HASH_THRESHOLD: usize = 16 * 1024;
|
||||
const SAMPLE_CHUNKS: usize = 8;
|
||||
const SAMPLE_CHUNK_BYTES: usize = 512;
|
||||
|
||||
let mut h = DefaultHasher::new();
|
||||
let n = bytes.len();
|
||||
n.hash( &mut h );
|
||||
if n <= FULL_HASH_THRESHOLD
|
||||
{
|
||||
bytes.hash( &mut h );
|
||||
} else {
|
||||
let stride = n / SAMPLE_CHUNKS;
|
||||
for i in 0..SAMPLE_CHUNKS
|
||||
{
|
||||
let pos = ( i * stride ).min( n - SAMPLE_CHUNK_BYTES );
|
||||
bytes[ pos..pos + SAMPLE_CHUNK_BYTES ].hash( &mut h );
|
||||
}
|
||||
}
|
||||
h.finish()
|
||||
}
|
||||
|
||||
impl GlesCanvas
|
||||
{
|
||||
/// Blit RGBA image data scaled to dest rect with opacity.
|
||||
|
||||
@@ -37,8 +37,8 @@ impl<A: App> AppData<A>
|
||||
if let Some( m ) = msg
|
||||
{
|
||||
self.pending_msgs.push( m );
|
||||
self.surface_mut( focus ).request_redraw();
|
||||
}
|
||||
self.surface_mut( focus ).request_redraw();
|
||||
}
|
||||
MoveOutcome::Scroll =>
|
||||
{
|
||||
|
||||
@@ -144,6 +144,9 @@ pub struct GestureState<Msg: Clone>
|
||||
/// the pointer Press handler right after [`Self::on_press`]; left
|
||||
/// `false` for touch presses.
|
||||
pub mouse_press: bool,
|
||||
/// Pattern-grid node the finger was last over, so `on_node` fires
|
||||
/// once per node rather than once per motion event.
|
||||
pub last_pattern_node: Option<u8>,
|
||||
}
|
||||
|
||||
impl<Msg: Clone> Default for GestureState<Msg>
|
||||
@@ -176,9 +179,22 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
long_press_fired: false,
|
||||
overscroll_y: 0.0,
|
||||
mouse_press: false,
|
||||
last_pattern_node: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A press that has not become anything else yet: no drag, scroll,
|
||||
/// swipe or slider in flight. Its release would be a tap.
|
||||
pub fn is_plain_press( &self ) -> bool
|
||||
{
|
||||
self.start.is_some()
|
||||
&& self.dragging_slider.is_none()
|
||||
&& !self.scroll_drag_started
|
||||
&& !self.horizontal_drag_started
|
||||
&& !self.vertical_drag_started
|
||||
&& !self.long_press_fired
|
||||
}
|
||||
|
||||
/// Press / touch-down: record the gesture origin, capture long-press
|
||||
/// handler if the hit widget has one, identify slider / scroll
|
||||
/// targets. Returns the hit index (for keyboard focus update) and
|
||||
@@ -256,8 +272,10 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
let value = w.handlers.slider_value_from_pos( w.rect, pos );
|
||||
initial_slider_msg = w.handlers.slider_change_msg( value );
|
||||
} else if w.handlers.is_drag_target() {
|
||||
self.dragging_slider = Some( idx );
|
||||
initial_slider_msg = w.handlers.pattern_node_msg( w.rect, pos );
|
||||
self.dragging_slider = Some( idx );
|
||||
let node = w.handlers.pattern_node_at( w.rect, pos );
|
||||
self.last_pattern_node = node;
|
||||
initial_slider_msg = node.and_then( |n| w.handlers.pattern_msg( n ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,7 +321,7 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
if let Some( origin ) = self.long_press_origin
|
||||
{
|
||||
let d = ( pos.x - origin.x ).hypot( pos.y - origin.y );
|
||||
if d > 6.0
|
||||
if d > swipe.stray()
|
||||
{
|
||||
self.long_press_start = None;
|
||||
self.long_press_origin = None;
|
||||
@@ -324,7 +342,10 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
let value = w.handlers.slider_value_from_pos( w.rect, pos );
|
||||
w.handlers.slider_change_msg( value )
|
||||
} else {
|
||||
w.handlers.pattern_node_msg( w.rect, pos )
|
||||
let node = w.handlers.pattern_node_at( w.rect, pos );
|
||||
if node == self.last_pattern_node { return MoveOutcome::Idle; }
|
||||
self.last_pattern_node = node;
|
||||
node.and_then( |n| w.handlers.pattern_msg( n ) )
|
||||
};
|
||||
return MoveOutcome::Slider { msg };
|
||||
}
|
||||
@@ -340,7 +361,7 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
|
||||
if !self.scroll_locked
|
||||
{
|
||||
if dx.abs() <= 8.0 && dy.abs() <= 8.0
|
||||
if dx.abs() <= swipe.slop() && dy.abs() <= swipe.slop()
|
||||
{
|
||||
return MoveOutcome::Idle;
|
||||
}
|
||||
@@ -381,7 +402,7 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
let moved = if axis.allows_x() && axis.allows_y() { dx.hypot( dy ) }
|
||||
else if axis.allows_x() { dx.abs() }
|
||||
else { dy.abs() };
|
||||
if moved > 8.0 { self.scroll_drag_started = true; }
|
||||
if moved > swipe.slop() { self.scroll_drag_started = true; }
|
||||
self.start = Some( pos );
|
||||
// Gate the dismiss on the same 8 px arm as a real scroll drag so
|
||||
// tap jitter at the top can't engage a follow-the-finger close.
|
||||
@@ -409,7 +430,7 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
|
||||
// Lock onto the dominant axis on the first 8 px of travel so
|
||||
// vertical and horizontal swipes stay mutually exclusive.
|
||||
if self.swipe_axis.is_none() && dx.abs().max( dy.abs() ) > 8.0
|
||||
if self.swipe_axis.is_none() && dx.abs().max( dy.abs() ) > swipe.slop()
|
||||
{
|
||||
self.swipe_axis = Some( if dy.abs() >= dx.abs() { SwipeAxis::Vertical } else { SwipeAxis::Horizontal } );
|
||||
}
|
||||
@@ -421,7 +442,7 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
if dy > 0.0
|
||||
{
|
||||
let threshold = h * swipe.up_thresh;
|
||||
if dy.abs() > 8.0 { self.vertical_drag_started = true; }
|
||||
if dy.abs() > swipe.slop() { self.vertical_drag_started = true; }
|
||||
// Unclamped on purpose — lets follow-the-finger
|
||||
// panels track past the commit threshold.
|
||||
( Some( ( dy / threshold ).max( 0.0 ) ), None )
|
||||
@@ -429,7 +450,7 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
else if start.y <= h * swipe.down_edge
|
||||
{
|
||||
let threshold = h * swipe.down_thresh;
|
||||
if dy.abs() > 8.0 { self.vertical_drag_started = true; }
|
||||
if dy.abs() > swipe.slop() { self.vertical_drag_started = true; }
|
||||
// Unclamped on purpose — lets follow-the-finger
|
||||
// panels track past the commit threshold.
|
||||
( None, Some( ( -dy / threshold ).max( 0.0 ) ) )
|
||||
@@ -501,6 +522,7 @@ impl<Msg: Clone> GestureState<Msg>
|
||||
self.mouse_press = false;
|
||||
self.horizontal_drag_started = false;
|
||||
self.vertical_drag_started = false;
|
||||
self.last_pattern_node = None;
|
||||
|
||||
let mut events = Vec::new();
|
||||
|
||||
|
||||
@@ -92,4 +92,22 @@ pub struct SwipeConfig
|
||||
pub overscroll_thresh: f32,
|
||||
pub surface_width: u32,
|
||||
pub surface_height: u32,
|
||||
/// Buffer scale of the surface (physical px per logical px), so the
|
||||
/// motion thresholds below stay a logical size on a HiDPI panel.
|
||||
pub scale: f32,
|
||||
}
|
||||
|
||||
impl SwipeConfig
|
||||
{
|
||||
/// Travel past which a press becomes a scroll, swipe or drag: 8 logical px.
|
||||
pub fn slop( &self ) -> f32
|
||||
{
|
||||
8.0 * self.scale.max( 1.0 )
|
||||
}
|
||||
|
||||
/// Travel past which a touch hold stops being a long-press candidate: 6 logical px.
|
||||
pub fn stray( &self ) -> f32
|
||||
{
|
||||
6.0 * self.scale.max( 1.0 )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ fn cfg_full( w: u32, h: u32 ) -> SwipeConfig
|
||||
overscroll_thresh: 0.25,
|
||||
surface_width: w,
|
||||
surface_height: h,
|
||||
scale: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ impl<A: App> AppData<A>
|
||||
overscroll_thresh: self.app.overscroll_dismiss_threshold(),
|
||||
surface_width: ss.physical_width(),
|
||||
surface_height: ss.physical_height(),
|
||||
scale: ss.scale_factor.max( 1 ) as f32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
-14
@@ -63,6 +63,23 @@ impl<A: App> TouchHandler for AppData<A>
|
||||
if self.is_secondary_surface( &surface ) { return; }
|
||||
let focus = self.focus_for_surface( &surface ).unwrap_or( SurfaceFocus::Main );
|
||||
self.touch_focus.insert( id, focus );
|
||||
|
||||
// A second finger landing on a plain press means the first was a
|
||||
// tap: commit it at its last position and hand the slot over, so
|
||||
// alternating thumbs never lose a key.
|
||||
if !self.app.claims_raw_touch()
|
||||
{
|
||||
let takeover =
|
||||
{
|
||||
let ss = self.surface( focus );
|
||||
ss.primary_touch_id.is_some_and( |primary| primary != id ) && ss.gesture.is_plain_press()
|
||||
};
|
||||
if takeover
|
||||
{
|
||||
self.release_primary( focus );
|
||||
}
|
||||
}
|
||||
|
||||
let pos = self.surface( focus ).to_physical( position.0, position.1 );
|
||||
self.pointer_pos = pos;
|
||||
|
||||
@@ -226,20 +243,7 @@ impl<A: App> TouchHandler for AppData<A>
|
||||
return;
|
||||
}
|
||||
|
||||
// Touch-up does not carry a position in wl_touch — the last
|
||||
// motion's position is the release point.
|
||||
let pos = self.pointer_pos;
|
||||
let global_drag = self.has_active_long_press_drag();
|
||||
let swipe = self.swipe_config( focus );
|
||||
let events_out =
|
||||
{
|
||||
let ss = self.surface_mut( focus );
|
||||
ss.needs_redraw = true;
|
||||
ss.primary_touch_id = None;
|
||||
ss.gesture.on_release( pos, &ss.frame.widget_rects, &swipe, global_drag )
|
||||
};
|
||||
self.apply_release_events( focus, events_out );
|
||||
self.stop_button_repeat();
|
||||
self.release_primary( focus );
|
||||
}
|
||||
|
||||
fn motion(
|
||||
@@ -334,6 +338,24 @@ impl<A: App> TouchHandler for AppData<A>
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
{
|
||||
/// Release the primary slot's gesture. Touch-up carries no position in
|
||||
/// wl_touch, so the last motion's position is the release point.
|
||||
fn release_primary( &mut self, focus: SurfaceFocus )
|
||||
{
|
||||
let pos = self.pointer_pos;
|
||||
let global_drag = self.has_active_long_press_drag();
|
||||
let swipe = self.swipe_config( focus );
|
||||
let events_out =
|
||||
{
|
||||
let ss = self.surface_mut( focus );
|
||||
ss.needs_redraw = true;
|
||||
ss.primary_touch_id = None;
|
||||
ss.gesture.on_release( pos, &ss.frame.widget_rects, &swipe, global_drag )
|
||||
};
|
||||
self.apply_release_events( focus, events_out );
|
||||
self.stop_button_repeat();
|
||||
}
|
||||
|
||||
/// Drop every in-flight touch gesture across all surfaces, notifying
|
||||
/// the app of any slot still down that it was tracking raw (auxiliary
|
||||
/// fingers, or every finger under `claims_raw_touch`). Shared by the
|
||||
|
||||
@@ -5,10 +5,40 @@
|
||||
//! rounded-rect construction. System-font resolution lives in
|
||||
//! `crate::system_fonts`.
|
||||
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{ Hash, Hasher };
|
||||
|
||||
use tiny_skia::{ Path, PathBuilder };
|
||||
|
||||
use crate::types::{ Corners, PathCmd };
|
||||
|
||||
/// A 64-bit fingerprint of an RGBA buffer, the key of both backends' image
|
||||
/// caches. Hashes the full slice below 16 KB (icons, thumbnails) and a
|
||||
/// strided 8 × 512-byte sample above it, so a wallpaper does not pay a
|
||||
/// full hash on every frame.
|
||||
pub( crate ) fn fingerprint_rgba( bytes: &[u8] ) -> u64
|
||||
{
|
||||
const FULL_HASH_THRESHOLD: usize = 16 * 1024;
|
||||
const SAMPLE_CHUNKS: usize = 8;
|
||||
const SAMPLE_CHUNK_BYTES: usize = 512;
|
||||
|
||||
let mut h = DefaultHasher::new();
|
||||
let n = bytes.len();
|
||||
n.hash( &mut h );
|
||||
if n <= FULL_HASH_THRESHOLD
|
||||
{
|
||||
bytes.hash( &mut h );
|
||||
} else {
|
||||
let stride = n / SAMPLE_CHUNKS;
|
||||
for i in 0..SAMPLE_CHUNKS
|
||||
{
|
||||
let pos = ( i * stride ).min( n - SAMPLE_CHUNK_BYTES );
|
||||
bytes[ pos..pos + SAMPLE_CHUNK_BYTES ].hash( &mut h );
|
||||
}
|
||||
}
|
||||
h.finish()
|
||||
}
|
||||
|
||||
/// Cubic bezier control-point factor for a quarter-circle approximation
|
||||
/// (`(4/3) * (sqrt(2) - 1) ≈ 0.5523`).
|
||||
const KAPPA: f32 = 0.5523_f32;
|
||||
|
||||
+122
-27
@@ -13,12 +13,110 @@
|
||||
//! (Abgr8888 matches tiny-skia's byte order) or swaps R/B in blocks
|
||||
//! of four pixels (Argb8888 fallback).
|
||||
|
||||
use tiny_skia::{ Pixmap, PixmapPaint, Transform };
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{ HashMap, VecDeque };
|
||||
|
||||
use tiny_skia::{ IntSize, Pixmap, PixmapPaint, Transform };
|
||||
|
||||
use crate::types::Rect;
|
||||
|
||||
use super::helpers::fingerprint_rgba;
|
||||
use super::SoftwareCanvas;
|
||||
|
||||
/// Byte budget of the premultiplied-image cache, matching the GLES texture
|
||||
/// cache: a phone wallpaper plus a working set of icons.
|
||||
const PREMUL_CACHE_MAX_BYTES: usize = 32 * 1024 * 1024;
|
||||
|
||||
type PremulKey = ( u32, u32, u64, u32 );
|
||||
|
||||
/// A premultiplied source and whether every pixel was fully opaque, which
|
||||
/// lets a 1:1 draw be a row copy.
|
||||
struct Premul
|
||||
{
|
||||
pixmap: Pixmap,
|
||||
opaque: bool,
|
||||
}
|
||||
|
||||
/// Premultiplied copies of the images drawn recently, keyed by size,
|
||||
/// content fingerprint and alpha, so a backdrop or a logo is converted once
|
||||
/// rather than on every frame. Least-recently-drawn eviction; the entry
|
||||
/// being drawn is never evicted.
|
||||
#[ derive( Default ) ]
|
||||
struct PremulCache
|
||||
{
|
||||
entries: HashMap<PremulKey, Premul>,
|
||||
lru: VecDeque<PremulKey>,
|
||||
bytes: usize,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static PREMUL_CACHE: RefCell<PremulCache> = RefCell::new( PremulCache::default() );
|
||||
}
|
||||
|
||||
impl PremulCache
|
||||
{
|
||||
/// Mark `key` as just used. `false` when it is not cached.
|
||||
fn touch( &mut self, key: &PremulKey ) -> bool
|
||||
{
|
||||
let Some( pos ) = self.lru.iter().position( |k| k == key ) else { return false };
|
||||
self.lru.remove( pos );
|
||||
self.lru.push_back( *key );
|
||||
true
|
||||
}
|
||||
|
||||
fn insert( &mut self, key: PremulKey, premul: Premul )
|
||||
{
|
||||
self.bytes += premul.pixmap.data().len();
|
||||
self.entries.insert( key, premul );
|
||||
self.lru.push_back( key );
|
||||
while self.bytes > PREMUL_CACHE_MAX_BYTES && self.lru.len() > 1
|
||||
{
|
||||
let Some( old ) = self.lru.pop_front() else { break };
|
||||
if let Some( p ) = self.entries.remove( &old )
|
||||
{
|
||||
self.bytes = self.bytes.saturating_sub( p.pixmap.data().len() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn premultiply( rgba: &[u8], size: IntSize, alpha: f32 ) -> Option<Premul>
|
||||
{
|
||||
let mut out = vec![ 0u8; rgba.len() ];
|
||||
let mut opaque = alpha >= 1.0;
|
||||
for ( dst, src ) in out.chunks_exact_mut( 4 ).zip( rgba.chunks_exact( 4 ) )
|
||||
{
|
||||
opaque &= src[3] == 255;
|
||||
let a = ( src[3] as f32 / 255.0 ) * alpha;
|
||||
dst[0] = ( src[0] as f32 * a ) as u8;
|
||||
dst[1] = ( src[1] as f32 * a ) as u8;
|
||||
dst[2] = ( src[2] as f32 * a ) as u8;
|
||||
dst[3] = ( a * 255.0 ) as u8;
|
||||
}
|
||||
Pixmap::from_vec( out, size ).map( |pixmap| Premul { pixmap, opaque } )
|
||||
}
|
||||
|
||||
/// Copy `src` onto `dst` at (`x`, `y`) row by row, clipped to `dst`.
|
||||
fn copy_rows( dst: &mut Pixmap, src: &Pixmap, x: i32, y: i32 )
|
||||
{
|
||||
let ( dw, dh ) = ( dst.width() as i32, dst.height() as i32 );
|
||||
let ( sw, sh ) = ( src.width() as i32, src.height() as i32 );
|
||||
let x0 = x.max( 0 );
|
||||
let x1 = ( x + sw ).min( dw );
|
||||
let y0 = y.max( 0 );
|
||||
let y1 = ( y + sh ).min( dh );
|
||||
if x0 >= x1 || y0 >= y1 { return; }
|
||||
let row_bytes = ( ( x1 - x0 ) * 4 ) as usize;
|
||||
let src_data = src.data();
|
||||
let dst_data = dst.data_mut();
|
||||
for row in y0..y1
|
||||
{
|
||||
let s = ( ( ( row - y ) * sw + ( x0 - x ) ) * 4 ) as usize;
|
||||
let d = ( ( row * dw + x0 ) * 4 ) as usize;
|
||||
dst_data[ d..d + row_bytes ].copy_from_slice( &src_data[ s..s + row_bytes ] );
|
||||
}
|
||||
}
|
||||
|
||||
impl SoftwareCanvas
|
||||
{
|
||||
pub fn draw_image_data( &mut self, rgba_data: &[u8], img_w: u32, img_h: u32, dest: Rect, opacity: f32 )
|
||||
@@ -41,37 +139,34 @@ impl SoftwareCanvas
|
||||
|
||||
let Some( int_size ) = tiny_skia::IntSize::from_wh( img_w, img_h ) else { return };
|
||||
|
||||
thread_local! {
|
||||
static PREMUL_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new( Vec::new() );
|
||||
}
|
||||
|
||||
PREMUL_BUF.with( |cell|
|
||||
let alpha = opacity * self.global_alpha;
|
||||
let key = ( img_w, img_h, fingerprint_rgba( rgba_data ), alpha.to_bits() );
|
||||
PREMUL_CACHE.with( |cell|
|
||||
{
|
||||
let mut premul = cell.borrow_mut();
|
||||
let needed = rgba_data.len();
|
||||
premul.resize( needed, 0 );
|
||||
for ( dst, src ) in premul.chunks_exact_mut( 4 ).zip( rgba_data.chunks_exact( 4 ) )
|
||||
let mut cache = cell.borrow_mut();
|
||||
if !cache.touch( &key )
|
||||
{
|
||||
let a = (src[3] as f32 / 255.0) * opacity * self.global_alpha;
|
||||
dst[0] = (src[0] as f32 * a) as u8;
|
||||
dst[1] = (src[1] as f32 * a) as u8;
|
||||
dst[2] = (src[2] as f32 * a) as u8;
|
||||
dst[3] = (a * 255.0) as u8;
|
||||
let Some( premul ) = premultiply( rgba_data, int_size, alpha ) else { return };
|
||||
cache.insert( key, premul );
|
||||
}
|
||||
let Some( src ) = cache.entries.get( &key ) else { return };
|
||||
|
||||
let unscaled = dest.width as u32 == img_w && dest.height as u32 == img_h;
|
||||
if src.opaque && unscaled && self.clip_mask.is_none()
|
||||
{
|
||||
copy_rows( &mut self.pixmap, &src.pixmap, dest.x as i32, dest.y as i32 );
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some( src_pixmap ) = Pixmap::from_vec( std::mem::take( &mut *premul ), int_size )
|
||||
let sx = dest.width / img_w as f32;
|
||||
let sy = dest.height / img_h as f32;
|
||||
let t = Transform::from_scale( sx, sy ).post_translate( dest.x, dest.y );
|
||||
let paint = PixmapPaint
|
||||
{
|
||||
let sx = dest.width / img_w as f32;
|
||||
let sy = dest.height / img_h as f32;
|
||||
let t = Transform::from_scale( sx, sy ).post_translate( dest.x, dest.y );
|
||||
let paint = PixmapPaint
|
||||
{
|
||||
quality: tiny_skia::FilterQuality::Bilinear,
|
||||
..PixmapPaint::default()
|
||||
};
|
||||
self.pixmap.draw_pixmap( 0, 0, src_pixmap.as_ref(), &paint, t, self.clip_mask.as_ref() );
|
||||
*premul = src_pixmap.take();
|
||||
}
|
||||
quality: tiny_skia::FilterQuality::Bilinear,
|
||||
..PixmapPaint::default()
|
||||
};
|
||||
self.pixmap.draw_pixmap( 0, 0, src.pixmap.as_ref(), &paint, t, self.clip_mask.as_ref() );
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
+19
-4
@@ -210,17 +210,32 @@ impl<Msg: Clone> WidgetHandlers<Msg>
|
||||
matches!( self, WidgetHandlers::Slider { .. } | WidgetHandlers::PatternGrid { .. } )
|
||||
}
|
||||
|
||||
/// The message a pattern grid fires for the node under `pos`, if any.
|
||||
pub fn pattern_node_msg( &self, rect: Rect, pos: Point ) -> Option<Msg>
|
||||
/// The pattern-grid node under `pos`, within the snap radius.
|
||||
pub fn pattern_node_at( &self, rect: Rect, pos: Point ) -> Option<u8>
|
||||
{
|
||||
match self
|
||||
{
|
||||
WidgetHandlers::PatternGrid { on_node: Some( f ), snap_px, .. } =>
|
||||
super::pattern_grid::node_at( rect, pos, *snap_px ).map( |node| f( node ) ),
|
||||
WidgetHandlers::PatternGrid { snap_px, .. } => super::pattern_grid::node_at( rect, pos, *snap_px ),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The message a pattern grid fires for `node`, if it has an `on_node`.
|
||||
pub fn pattern_msg( &self, node: u8 ) -> Option<Msg>
|
||||
{
|
||||
match self
|
||||
{
|
||||
WidgetHandlers::PatternGrid { on_node: Some( f ), .. } => Some( f( node ) ),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The message a pattern grid fires for the node under `pos`, if any.
|
||||
pub fn pattern_node_msg( &self, rect: Rect, pos: Point ) -> Option<Msg>
|
||||
{
|
||||
self.pattern_node_at( rect, pos ).and_then( |node| self.pattern_msg( node ) )
|
||||
}
|
||||
|
||||
pub fn pattern_release_msg( &self ) -> Option<Msg>
|
||||
{
|
||||
match self
|
||||
|
||||
Reference in New Issue
Block a user