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 scroll = self.text_scroll( focus, widget_idx );
|
||||||
let canvas = self.surface( focus ).canvas.as_ref()?;
|
let canvas = self.surface( focus ).canvas.as_ref()?;
|
||||||
Some( crate::widget::text_edit::byte_offset_at(
|
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
|
if self.pending_size_hint_unpin
|
||||||
{
|
{
|
||||||
|
window.set_min_size( None );
|
||||||
window.set_max_size( None );
|
window.set_max_size( None );
|
||||||
self.pending_size_hint_unpin = false;
|
self.pending_size_hint_unpin = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub( crate ) mod drag;
|
|||||||
pub( crate ) mod focus;
|
pub( crate ) mod focus;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
pub( crate ) mod osk;
|
pub( crate ) mod osk;
|
||||||
|
pub( crate ) mod output_probe;
|
||||||
pub( crate ) mod perf;
|
pub( crate ) mod perf;
|
||||||
pub( crate ) mod repeat;
|
pub( crate ) mod repeat;
|
||||||
pub( crate ) mod session;
|
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 );
|
.unwrap_or( value_handler );
|
||||||
Some( ( widget.rect, value, multiline, secure, align, font_size ) )
|
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
|
/// 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 ss = self.surface( focus );
|
||||||
let canvas = match ss.canvas.as_ref() { Some( c ) => c, None => return };
|
let canvas = match ss.canvas.as_ref() { Some( c ) => c, None => return };
|
||||||
crate::widget::text_edit::byte_offset_at(
|
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 );
|
let ( start, end ) = word_bounds_at( &value, click_byte );
|
||||||
@@ -141,7 +141,7 @@ impl<A: App> AppData<A>
|
|||||||
let ss = self.surface( focus );
|
let ss = self.surface( focus );
|
||||||
let canvas = match ss.canvas.as_ref() { Some( c ) => c, None => return };
|
let canvas = match ss.canvas.as_ref() { Some( c ) => c, None => return };
|
||||||
crate::widget::text_edit::byte_offset_at(
|
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 );
|
let ss = self.surface_mut( focus );
|
||||||
@@ -176,7 +176,7 @@ impl<A: App> AppData<A>
|
|||||||
let ss = self.surface( focus );
|
let ss = self.surface( focus );
|
||||||
let canvas = match ss.canvas.as_ref() { Some( c ) => c, None => return };
|
let canvas = match ss.canvas.as_ref() { Some( c ) => c, None => return };
|
||||||
crate::widget::text_edit::byte_offset_at(
|
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 );
|
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`.
|
/// Share of an output a window may take when the compositor sent no `configure_bounds`.
|
||||||
pub( crate ) const OUTPUT_SHARE: f32 = 0.9;
|
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`.
|
/// `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 )
|
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 )>
|
fn output_bounds( &self ) -> Option<( u32, u32 )>
|
||||||
{
|
{
|
||||||
let ( w, h ) = self.output_logical_size( None )?;
|
self.output_logical_size( None ).map( output_share )
|
||||||
Some( ( ( w as f32 * OUTPUT_SHARE ) as u32, ( h as f32 * OUTPUT_SHARE ) as u32 ) )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output_logical_size( &self, preferred: Option<&WlOutput> ) -> Option<( u32, u32 )>
|
fn output_logical_size( &self, preferred: Option<&WlOutput> ) -> Option<( u32, u32 )>
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
//!
|
//!
|
||||||
//! The first finger to land on a surface becomes its **primary slot**
|
//! The first finger to land on a surface becomes its **primary slot**
|
||||||
//! and drives the single-slot gesture machine (swipe, scroll,
|
//! and drives the single-slot gesture machine (swipe, scroll,
|
||||||
//! long-press, drag). Any additional finger arriving while the
|
//! long-press, drag); like the left mouse button, it is also reported
|
||||||
|
//! through [`App::on_pointer_button`] / [`App::on_pointer_move`]. Any additional finger arriving while the
|
||||||
//! primary is held bypasses the gesture machine and surfaces directly
|
//! primary is held bypasses the gesture machine and surfaces directly
|
||||||
//! through [`App::on_touch_down`] / [`App::on_touch_move`] /
|
//! through [`App::on_touch_down`] / [`App::on_touch_move`] /
|
||||||
//! [`App::on_touch_up`], so apps can implement pinch-zoom or
|
//! [`App::on_touch_up`], so apps can implement pinch-zoom or
|
||||||
@@ -153,6 +154,9 @@ impl<A: App> TouchHandler for AppData<A>
|
|||||||
}
|
}
|
||||||
if self.osk_press( focus, pos ) { return; }
|
if self.osk_press( focus, pos ) { return; }
|
||||||
|
|
||||||
|
self.app.on_pointer_move( pos.x, pos.y );
|
||||||
|
self.app.on_pointer_button( pos.x, pos.y, true );
|
||||||
|
|
||||||
let outcome =
|
let outcome =
|
||||||
{
|
{
|
||||||
let ss = self.surface_mut( focus );
|
let ss = self.surface_mut( focus );
|
||||||
@@ -305,6 +309,7 @@ impl<A: App> TouchHandler for AppData<A>
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.pointer_pos = pp;
|
self.pointer_pos = pp;
|
||||||
|
self.app.on_pointer_move( pp.x, pp.y );
|
||||||
if self.osk_move( focus, pp ) { return; }
|
if self.osk_move( focus, pp ) { return; }
|
||||||
let global_drag = self.has_active_long_press_drag();
|
let global_drag = self.has_active_long_press_drag();
|
||||||
let swipe = self.swipe_config( focus );
|
let swipe = self.swipe_config( focus );
|
||||||
@@ -367,6 +372,7 @@ impl<A: App> AppData<A>
|
|||||||
self.surface_mut( focus ).primary_touch_id = None;
|
self.surface_mut( focus ).primary_touch_id = None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
self.app.on_pointer_button( pos.x, pos.y, false );
|
||||||
let global_drag = self.has_active_long_press_drag();
|
let global_drag = self.has_active_long_press_drag();
|
||||||
let swipe = self.swipe_config( focus );
|
let swipe = self.swipe_config( focus );
|
||||||
let events_out =
|
let events_out =
|
||||||
|
|||||||
@@ -325,6 +325,7 @@ impl<Msg: Clone> Element<Msg>
|
|||||||
multiline: t.is_multiline(),
|
multiline: t.is_multiline(),
|
||||||
align: t.align,
|
align: t.align,
|
||||||
font_size: t.font_size,
|
font_size: t.font_size,
|
||||||
|
pad_h: t.pad_h,
|
||||||
select_on_focus: t.select_on_focus,
|
select_on_focus: t.select_on_focus,
|
||||||
password_toggle_msg: t.password_toggle.as_ref().map( |( _, m )| m.clone() ),
|
password_toggle_msg: t.password_toggle.as_ref().map( |( _, m )| m.clone() ),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ pub enum WidgetHandlers<Msg: Clone>
|
|||||||
/// drew them. `None` for fields that do not call
|
/// drew them. `None` for fields that do not call
|
||||||
/// `.font_size( … )` (they follow the widget-scaling mode).
|
/// `.font_size( … )` (they follow the widget-scaling mode).
|
||||||
font_size: Option<Length>,
|
font_size: Option<Length>,
|
||||||
|
/// Horizontal inner padding override snapshot, for the same hit-testing.
|
||||||
|
pad_h: Option<f32>,
|
||||||
/// `true` when the source field opted into select-all-on-
|
/// `true` when the source field opted into select-all-on-
|
||||||
/// focus. The runtime reads this in `set_focus` to decide
|
/// focus. The runtime reads this in `set_focus` to decide
|
||||||
/// whether the new selection should anchor at `0` (replace
|
/// whether the new selection should anchor at `0` (replace
|
||||||
@@ -142,7 +144,7 @@ impl<Msg: Clone> Clone for WidgetHandlers<Msg>
|
|||||||
WidgetHandlers::Radio { on_select, selected } => WidgetHandlers::Radio { on_select: on_select.clone(), selected: *selected },
|
WidgetHandlers::Radio { on_select, selected } => WidgetHandlers::Radio { on_select: on_select.clone(), selected: *selected },
|
||||||
WidgetHandlers::ListItem { on_press } => WidgetHandlers::ListItem { on_press: on_press.clone() },
|
WidgetHandlers::ListItem { on_press } => WidgetHandlers::ListItem { on_press: on_press.clone() },
|
||||||
WidgetHandlers::WindowButton { on_press } => WidgetHandlers::WindowButton { on_press: on_press.clone() },
|
WidgetHandlers::WindowButton { on_press } => WidgetHandlers::WindowButton { on_press: on_press.clone() },
|
||||||
WidgetHandlers::TextEdit { value, on_change, on_submit, on_focus, secure, purpose, multiline, align, font_size, select_on_focus, password_toggle_msg } =>
|
WidgetHandlers::TextEdit { value, on_change, on_submit, on_focus, secure, purpose, multiline, align, font_size, pad_h, select_on_focus, password_toggle_msg } =>
|
||||||
{
|
{
|
||||||
WidgetHandlers::TextEdit
|
WidgetHandlers::TextEdit
|
||||||
{
|
{
|
||||||
@@ -155,6 +157,7 @@ impl<Msg: Clone> Clone for WidgetHandlers<Msg>
|
|||||||
multiline: *multiline,
|
multiline: *multiline,
|
||||||
align: *align,
|
align: *align,
|
||||||
font_size: *font_size,
|
font_size: *font_size,
|
||||||
|
pad_h: *pad_h,
|
||||||
select_on_focus: *select_on_focus,
|
select_on_focus: *select_on_focus,
|
||||||
password_toggle_msg: password_toggle_msg.clone(),
|
password_toggle_msg: password_toggle_msg.clone(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ fn password_toggle_reserve( rect: Rect ) -> f32
|
|||||||
pub fn password_toggle_hit_zone( rect: Rect ) -> Rect
|
pub fn password_toggle_hit_zone( rect: Rect ) -> Rect
|
||||||
{
|
{
|
||||||
let zone_w = ( password_toggle_reserve( rect )
|
let zone_w = ( password_toggle_reserve( rect )
|
||||||
+ single_line_pad_h( rect ) * 0.5 ).min( rect.width );
|
+ single_line_pad_h( rect, None ) * 0.5 ).min( rect.width );
|
||||||
Rect
|
Rect
|
||||||
{
|
{
|
||||||
x: rect.x + rect.width - zone_w,
|
x: rect.x + rect.width - zone_w,
|
||||||
@@ -123,14 +123,14 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
{
|
{
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
let pad_h = single_line_pad_h( rect );
|
let pad_h = single_line_pad_h( rect, self.pad_h );
|
||||||
let text_rect = Rect
|
let text_rect = Rect
|
||||||
{
|
{
|
||||||
width: ( rect.width - toggle_reserve ).max( pad_h * 2.0 ),
|
width: ( rect.width - toggle_reserve ).max( pad_h * 2.0 ),
|
||||||
..rect
|
..rect
|
||||||
};
|
};
|
||||||
let scroll_x = single_line_scroll_x( canvas, text_rect, &self.value, cursor_pos, secure, font_size );
|
let scroll_x = single_line_scroll_x( canvas, text_rect, &self.value, cursor_pos, secure, font_size, self.pad_h );
|
||||||
let align_x = single_line_align_offset( canvas, text_rect, &self.value, secure, self.align, font_size );
|
let align_x = single_line_align_offset( canvas, text_rect, &self.value, secure, self.align, font_size, self.pad_h );
|
||||||
|
|
||||||
// Clip text / cursor / selection to the inner band so the
|
// Clip text / cursor / selection to the inner band so the
|
||||||
// scrolled-off portion does not bleed past the pill stroke.
|
// scrolled-off portion does not bleed past the pill stroke.
|
||||||
@@ -184,7 +184,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
// would, so an empty centred / right-aligned field still
|
// would, so an empty centred / right-aligned field still
|
||||||
// reads with the placeholder where the digits will land.
|
// reads with the placeholder where the digits will land.
|
||||||
let placeholder_align_x = single_line_align_offset(
|
let placeholder_align_x = single_line_align_offset(
|
||||||
canvas, rect, &self.placeholder, false, self.align, font_size,
|
canvas, rect, &self.placeholder, false, self.align, font_size, self.pad_h,
|
||||||
);
|
);
|
||||||
canvas.draw_text(
|
canvas.draw_text(
|
||||||
&self.placeholder,
|
&self.placeholder,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ pub( crate ) fn byte_offset_at(
|
|||||||
scroll: TextScroll,
|
scroll: TextScroll,
|
||||||
align: crate::widget::text::TextAlign,
|
align: crate::widget::text::TextAlign,
|
||||||
font_size: Option<Length>,
|
font_size: Option<Length>,
|
||||||
|
pad_h: Option<f32>,
|
||||||
) -> usize
|
) -> usize
|
||||||
{
|
{
|
||||||
if value.is_empty() { return 0; }
|
if value.is_empty() { return 0; }
|
||||||
@@ -51,11 +52,11 @@ pub( crate ) fn byte_offset_at(
|
|||||||
// applies so a click lands on the glyph the user actually
|
// applies so a click lands on the glyph the user actually
|
||||||
// sees, not on the byte offset that would correspond to an
|
// sees, not on the byte offset that would correspond to an
|
||||||
// unscrolled, left-aligned layout.
|
// unscrolled, left-aligned layout.
|
||||||
let scroll_x = single_line_scroll_x( canvas, rect, value, cursor_pos, secure, font_size );
|
let scroll_x = single_line_scroll_x( canvas, rect, value, cursor_pos, secure, font_size, pad_h );
|
||||||
let align_x = single_line_align_offset( canvas, rect, value, secure, align, font_size );
|
let align_x = single_line_align_offset( canvas, rect, value, secure, align, font_size, pad_h );
|
||||||
byte_offset_in_line(
|
byte_offset_in_line(
|
||||||
canvas, value, 0, value.len(),
|
canvas, value, 0, value.len(),
|
||||||
pos.x - rect.x - single_line_pad_h( rect ) - align_x + scroll_x,
|
pos.x - rect.x - single_line_pad_h( rect, pad_h ) - align_x + scroll_x,
|
||||||
secure,
|
secure,
|
||||||
font_size,
|
font_size,
|
||||||
)
|
)
|
||||||
@@ -86,9 +87,10 @@ pub( crate ) fn single_line_scroll_x(
|
|||||||
cursor: usize,
|
cursor: usize,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
|
pad_h: Option<f32>,
|
||||||
) -> f32
|
) -> f32
|
||||||
{
|
{
|
||||||
let inner_width = ( rect.width - single_line_pad_h( rect ) * 2.0 ).max( font_size );
|
let inner_width = ( rect.width - single_line_pad_h( rect, pad_h ) * 2.0 ).max( font_size );
|
||||||
let safe_cursor = cursor.min( value.len() );
|
let safe_cursor = cursor.min( value.len() );
|
||||||
|
|
||||||
let display_value: String = if secure
|
let display_value: String = if secure
|
||||||
@@ -133,11 +135,12 @@ pub( crate ) fn single_line_align_offset(
|
|||||||
secure: bool,
|
secure: bool,
|
||||||
align: crate::widget::text::TextAlign,
|
align: crate::widget::text::TextAlign,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
|
pad_h: Option<f32>,
|
||||||
) -> f32
|
) -> f32
|
||||||
{
|
{
|
||||||
use crate::widget::text::TextAlign;
|
use crate::widget::text::TextAlign;
|
||||||
if matches!( align, TextAlign::Left ) { return 0.0; }
|
if matches!( align, TextAlign::Left ) { return 0.0; }
|
||||||
let inner_width = ( rect.width - single_line_pad_h( rect ) * 2.0 ).max( font_size );
|
let inner_width = ( rect.width - single_line_pad_h( rect, pad_h ) * 2.0 ).max( font_size );
|
||||||
let display_value: String = if secure
|
let display_value: String = if secure
|
||||||
{
|
{
|
||||||
"\u{2022}".repeat( value.chars().count() )
|
"\u{2022}".repeat( value.chars().count() )
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ pub( crate ) fn single_line_scale( rect: Rect ) -> f32
|
|||||||
rect.height / theme::HEIGHT
|
rect.height / theme::HEIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
pub( crate ) fn single_line_pad_h( rect: Rect ) -> f32
|
pub( crate ) fn single_line_pad_h( rect: Rect, pad_h: Option<f32> ) -> f32
|
||||||
{
|
{
|
||||||
theme::PAD_H * single_line_scale( rect )
|
pad_h.unwrap_or_else( || theme::PAD_H * single_line_scale( rect ) )
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolved layout of a multiline field, shared by sizing, drawing,
|
/// Resolved layout of a multiline field, shared by sizing, drawing,
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ pub struct TextEdit<Msg: Clone>
|
|||||||
/// to fit a fixed number of glyphs (date / time pickers, inline
|
/// to fit a fixed number of glyphs (date / time pickers, inline
|
||||||
/// numeric inputs).
|
/// numeric inputs).
|
||||||
pub( crate ) fixed_width: Option<Length>,
|
pub( crate ) fixed_width: Option<Length>,
|
||||||
|
/// Horizontal inner padding of the single-line field in physical pixels; `None` scales the theme padding with the field height.
|
||||||
|
pub( crate ) pad_h: Option<f32>,
|
||||||
/// Font size in pixels for the single-line draw path. `0.0` (the
|
/// Font size in pixels for the single-line draw path. `0.0` (the
|
||||||
/// Label font size. `None` follows the process [`crate::WidgetScaling`]
|
/// Label font size. `None` follows the process [`crate::WidgetScaling`]
|
||||||
/// mode at the theme default (via [`crate::Canvas::font_px`]); a
|
/// mode at the theme default (via [`crate::Canvas::font_px`]); a
|
||||||
@@ -214,6 +216,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
align: super::text::TextAlign::Left,
|
align: super::text::TextAlign::Left,
|
||||||
borderless: false,
|
borderless: false,
|
||||||
fixed_width: None,
|
fixed_width: None,
|
||||||
|
pad_h: None,
|
||||||
font_size: None,
|
font_size: None,
|
||||||
height: None,
|
height: None,
|
||||||
select_on_focus: false,
|
select_on_focus: false,
|
||||||
@@ -310,6 +313,16 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the horizontal inner padding of a single-line field, in
|
||||||
|
/// physical pixels, for a host that lays out its own (`0` lets the
|
||||||
|
/// text use the whole rect). Without it the padding follows the
|
||||||
|
/// field height.
|
||||||
|
pub fn padding_h( mut self, px: f32 ) -> Self
|
||||||
|
{
|
||||||
|
self.pad_h = Some( px.max( 0.0 ) );
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Override the preferred width reported to the parent layout.
|
/// Override the preferred width reported to the parent layout.
|
||||||
/// Accepts logical `f32` pixels or any [`Length`] — pair a
|
/// Accepts logical `f32` pixels or any [`Length`] — pair a
|
||||||
/// [`Length::fluid`] width with a fluid font size so box and glyphs
|
/// [`Length::fluid`] width with a fluid font size so box and glyphs
|
||||||
@@ -508,7 +521,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
) -> usize
|
) -> usize
|
||||||
{
|
{
|
||||||
byte_offset_at(
|
byte_offset_at(
|
||||||
canvas, rect, pos, &self.value, self.is_multiline(), self.effective_secure(), cursor_pos, scroll, self.align, self.font_size,
|
canvas, rect, pos, &self.value, self.is_multiline(), self.effective_secure(), cursor_pos, scroll, self.align, self.font_size, self.pad_h,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user