A text field can take its horizontal padding from its host, the primary finger reaches the pointer hooks, and the screen probe left out of the previous commit is added
The previous commit described the size hint fitted to the screen before the first commit and the pointer hooks being called for the primary finger, but left out the files that do it, so `run.rs` called an `output_probe` module that did not exist. They are here, together with a text field option that a host laying out its own padding needs. Text field padding. A single-line `TextEdit` insets its text by `PAD_H` scaled with the field height, so a tall and narrow field has almost no room left for its text: rustdroid's Android fields, which already subtract the padding the Android view asked for, showed a verification code digit in a 24 px wide gap minus ltk's own inset, scrolled it as if it overflowed and clipped half of it. `TextEdit::padding_h( px )` sets that inset in physical pixels; `0` lets the text use the whole rect. Without it nothing changes. `single_line_pad_h` takes the override and every single-line consumer passes it: the draw path, `single_line_scroll_x`, `single_line_align_offset` and `byte_offset_at`, so a click still lands on the glyph that is drawn. The runtime sees it through a new `pad_h` field in `WidgetHandlers::TextEdit`, read by `text_input_pad` where a click is turned into a text position (pointer down, word selection, drag selection and the context menu's paste offset). The password toggle zone keeps the theme inset. The screen probe. `output_probe::output_logical_size` reads the logical size of the first output on an event queue of its own, before any surface exists: `xdg_output`'s logical size when the compositor offers it, else the current mode over the scale; the objects it binds are destroyed or released before it returns. `window_fit::output_share` gives the part of an output a window may take, the same 90 % the fit after the first configure uses, which now goes through it too. When the size hint is unpinned on the first configure the minimum size is released along with the maximum, as the documentation of `window_resizable` says. Touch as pointer. The primary finger's down, motion and up are also reported through `App::on_pointer_move` and `App::on_pointer_button`, as the left mouse button is, so an application that handles the pointer gets the touch without a separate path. The module documentation says so.
This commit is contained in:
@@ -92,7 +92,7 @@ impl<A: App> AppData<A>
|
||||
let scroll = self.text_scroll( focus, widget_idx );
|
||||
let canvas = self.surface( focus ).canvas.as_ref()?;
|
||||
Some( crate::widget::text_edit::byte_offset_at(
|
||||
canvas, rect, pos, &value, multiline, secure, cursor_pos, scroll, align, font_size,
|
||||
canvas, rect, pos, &value, multiline, secure, cursor_pos, scroll, align, font_size, self.text_input_pad( focus, widget_idx ),
|
||||
) )
|
||||
} );
|
||||
|
||||
|
||||
@@ -295,6 +295,7 @@ impl<A: App> WindowHandler for AppData<A>
|
||||
}
|
||||
if self.pending_size_hint_unpin
|
||||
{
|
||||
window.set_min_size( None );
|
||||
window.set_max_size( None );
|
||||
self.pending_size_hint_unpin = false;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ pub( crate ) mod drag;
|
||||
pub( crate ) mod focus;
|
||||
mod handlers;
|
||||
pub( crate ) mod osk;
|
||||
pub( crate ) mod output_probe;
|
||||
pub( crate ) mod perf;
|
||||
pub( crate ) mod repeat;
|
||||
pub( crate ) mod session;
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
//! The screen's logical size before any surface exists, read on an event queue of its own.
|
||||
|
||||
use smithay_client_toolkit::reexports::client::{ Connection, Dispatch, Proxy, QueueHandle, WEnum };
|
||||
use smithay_client_toolkit::reexports::client::globals::GlobalList;
|
||||
use smithay_client_toolkit::reexports::client::protocol::wl_output::{ self, WlOutput };
|
||||
use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1;
|
||||
use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_v1::{ self, ZxdgOutputV1 };
|
||||
|
||||
struct Output
|
||||
{
|
||||
mode: Option<( i32, i32 )>,
|
||||
scale: i32,
|
||||
logical: Option<( i32, i32 )>,
|
||||
}
|
||||
|
||||
struct Probe
|
||||
{
|
||||
outputs: Vec<Output>,
|
||||
}
|
||||
|
||||
/// Logical size of the first output: `xdg_output`'s when the compositor offers it, else the current mode over the scale.
|
||||
pub( crate ) fn output_logical_size( conn: &Connection, globals: &GlobalList ) -> Option<( u32, u32 )>
|
||||
{
|
||||
let mut queue = conn.new_event_queue::<Probe>();
|
||||
let qh = queue.handle();
|
||||
let registry = globals.registry();
|
||||
let ( outputs, manager ) = globals.contents().with_list( |list|
|
||||
{
|
||||
let outputs: Vec<WlOutput> = list.iter()
|
||||
.filter( |g| g.interface == WlOutput::interface().name )
|
||||
.enumerate()
|
||||
.map( |( i, g )| registry.bind::<WlOutput, _, _>( g.name, g.version.min( 4 ), &qh, i ) )
|
||||
.collect();
|
||||
let manager = list.iter()
|
||||
.find( |g| g.interface == ZxdgOutputManagerV1::interface().name )
|
||||
.map( |g| registry.bind::<ZxdgOutputManagerV1, _, _>( g.name, g.version.min( 3 ), &qh, () ) );
|
||||
( outputs, manager )
|
||||
} );
|
||||
if outputs.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut xdg_outputs = Vec::new();
|
||||
if let Some( manager ) = &manager
|
||||
{
|
||||
for ( i, output ) in outputs.iter().enumerate()
|
||||
{
|
||||
xdg_outputs.push( manager.get_xdg_output( output, &qh, i ) );
|
||||
}
|
||||
}
|
||||
|
||||
let mut probe = Probe { outputs: outputs.iter().map( |_| Output { mode: None, scale: 1, logical: None } ).collect() };
|
||||
let answered = queue.roundtrip( &mut probe ).is_ok();
|
||||
|
||||
for xdg_output in xdg_outputs
|
||||
{
|
||||
xdg_output.destroy();
|
||||
}
|
||||
if let Some( manager ) = manager
|
||||
{
|
||||
manager.destroy();
|
||||
}
|
||||
for output in outputs
|
||||
{
|
||||
if output.version() >= 3
|
||||
{
|
||||
output.release();
|
||||
}
|
||||
}
|
||||
if !answered
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
probe.outputs.iter().find_map( |o|
|
||||
{
|
||||
let scale = o.scale.max( 1 );
|
||||
let ( w, h ) = o.logical.or_else( || o.mode.map( |( w, h )| ( w / scale, h / scale ) ) )?;
|
||||
( w > 0 && h > 0 ).then_some( ( w as u32, h as u32 ) )
|
||||
} )
|
||||
}
|
||||
|
||||
impl Dispatch<WlOutput, usize> for Probe
|
||||
{
|
||||
fn event( state: &mut Self, _: &WlOutput, event: wl_output::Event, index: &usize, _: &Connection, _: &QueueHandle<Self> )
|
||||
{
|
||||
let Some( output ) = state.outputs.get_mut( *index ) else { return };
|
||||
match event
|
||||
{
|
||||
wl_output::Event::Mode { flags: WEnum::Value( flags ), width, height, .. } if flags.contains( wl_output::Mode::Current ) =>
|
||||
{
|
||||
output.mode = Some( ( width, height ) );
|
||||
}
|
||||
wl_output::Event::Scale { factor } => output.scale = factor,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZxdgOutputManagerV1, ()> for Probe
|
||||
{
|
||||
fn event( _: &mut Self, _: &ZxdgOutputManagerV1, _: <ZxdgOutputManagerV1 as Proxy>::Event, _: &(), _: &Connection, _: &QueueHandle<Self> ) {}
|
||||
}
|
||||
|
||||
impl Dispatch<ZxdgOutputV1, usize> for Probe
|
||||
{
|
||||
fn event( state: &mut Self, _: &ZxdgOutputV1, event: zxdg_output_v1::Event, index: &usize, _: &Connection, _: &QueueHandle<Self> )
|
||||
{
|
||||
if let ( Some( output ), zxdg_output_v1::Event::LogicalSize { width, height } ) = ( state.outputs.get_mut( *index ), event )
|
||||
{
|
||||
output.logical = Some( ( width, height ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,16 @@ impl<A: App> AppData<A>
|
||||
.unwrap_or( value_handler );
|
||||
Some( ( widget.rect, value, multiline, secure, align, font_size ) )
|
||||
}
|
||||
|
||||
/// The horizontal inner padding override of the text field at `idx`, as its hit-testing needs.
|
||||
pub( crate ) fn text_input_pad( &self, focus: SurfaceFocus, idx: usize ) -> Option<f32>
|
||||
{
|
||||
match &find_widget( &self.surface( focus ).frame.widget_rects, idx )?.handlers
|
||||
{
|
||||
WidgetHandlers::TextEdit { pad_h, .. } => *pad_h,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a field's `secure` flag to the text-input-v3 content type. Secure
|
||||
|
||||
@@ -103,7 +103,7 @@ impl<A: App> AppData<A>
|
||||
let ss = self.surface( focus );
|
||||
let canvas = match ss.canvas.as_ref() { Some( c ) => c, None => return };
|
||||
crate::widget::text_edit::byte_offset_at(
|
||||
canvas, rect, pos, &value, multiline, secure, cursor_pos, scroll, align, font_size,
|
||||
canvas, rect, pos, &value, multiline, secure, cursor_pos, scroll, align, font_size, self.text_input_pad( focus, idx ),
|
||||
)
|
||||
};
|
||||
let ( start, end ) = word_bounds_at( &value, click_byte );
|
||||
@@ -141,7 +141,7 @@ impl<A: App> AppData<A>
|
||||
let ss = self.surface( focus );
|
||||
let canvas = match ss.canvas.as_ref() { Some( c ) => c, None => return };
|
||||
crate::widget::text_edit::byte_offset_at(
|
||||
canvas, rect, pos, &value, multiline, secure, cursor_pos, scroll, align, font_size,
|
||||
canvas, rect, pos, &value, multiline, secure, cursor_pos, scroll, align, font_size, self.text_input_pad( focus, idx ),
|
||||
)
|
||||
};
|
||||
let ss = self.surface_mut( focus );
|
||||
@@ -176,7 +176,7 @@ impl<A: App> AppData<A>
|
||||
let ss = self.surface( focus );
|
||||
let canvas = match ss.canvas.as_ref() { Some( c ) => c, None => return };
|
||||
crate::widget::text_edit::byte_offset_at(
|
||||
canvas, rect, pos, &value, multiline, secure, cursor_pos, scroll, align, font_size,
|
||||
canvas, rect, pos, &value, multiline, secure, cursor_pos, scroll, align, font_size, self.text_input_pad( focus, idx ),
|
||||
)
|
||||
};
|
||||
let ss = self.surface_mut( focus );
|
||||
|
||||
@@ -13,6 +13,12 @@ use super::AppData;
|
||||
/// Share of an output a window may take when the compositor sent no `configure_bounds`.
|
||||
pub( crate ) const OUTPUT_SHARE: f32 = 0.9;
|
||||
|
||||
/// The part of an output of logical size `output` a window may take.
|
||||
pub( crate ) fn output_share( output: ( u32, u32 ) ) -> ( u32, u32 )
|
||||
{
|
||||
( ( output.0 as f32 * OUTPUT_SHARE ) as u32, ( output.1 as f32 * OUTPUT_SHARE ) as u32 )
|
||||
}
|
||||
|
||||
/// `size` scaled down, keeping its aspect ratio, until it fits in `bounds`.
|
||||
pub( crate ) fn fit_within( size: ( u32, u32 ), bounds: ( u32, u32 ) ) -> ( u32, u32 )
|
||||
{
|
||||
@@ -43,8 +49,7 @@ impl<A: App> AppData<A>
|
||||
|
||||
fn output_bounds( &self ) -> Option<( u32, u32 )>
|
||||
{
|
||||
let ( w, h ) = self.output_logical_size( None )?;
|
||||
Some( ( ( w as f32 * OUTPUT_SHARE ) as u32, ( h as f32 * OUTPUT_SHARE ) as u32 ) )
|
||||
self.output_logical_size( None ).map( output_share )
|
||||
}
|
||||
|
||||
fn output_logical_size( &self, preferred: Option<&WlOutput> ) -> Option<( u32, u32 )>
|
||||
|
||||
Reference in New Issue
Block a user