text_edit: scale the single-line horizontal padding with the field height
The previous commit put the password eye, the caret and the whole multiline layout in physical pixels, but the single-line variant still inset its text by the raw `theme::PAD_H`: 20 physical px whatever the scale. On a scale-2 panel a field 88 px tall, drawn as a pill of radius 44, kept a 20 px inset, so the first glyph sat inside the rounded end while everything else in the field had grown. The padding now follows the field height, like the eye already did: `metrics::single_line_pad_h( rect )` is `PAD_H × rect.height / theme::HEIGHT`, built on `single_line_scale`, which replaces the private `password_toggle_scale` of `draw.rs`. The height is the right reference rather than `canvas.geom_px( PAD_H )`: the inset exists to clear the pill's rounded end, whose radius is half the height. With the default height the two agree, since that height is itself `geom_px( HEIGHT )`; when the app pins the height the padding follows that choice instead of the fluid mode, which on a large surface would push it to 1.5 times the design value next to a field the app deliberately kept smaller. It also needs no canvas, so `password_toggle_hit_zone( rect )` keeps its signature. `draw` resolves the padding once and uses it for the narrowed text rect, the inner clip, the selection band, the placeholder, the text and the caret; `password_toggle_hit_zone` uses it for the half padding beside the eye. `single_line_scroll_x`, `single_line_align_offset` and the single-line branch of `byte_offset_at` use the same helper, so a click still maps to the glyph that was drawn. `hit_test.rs` no longer references `theme` and drops the import. A field `HEIGHT` tall at scale 1 keeps exactly the previous geometry.
This commit is contained in:
@@ -6,7 +6,7 @@ use crate::types::Rect;
|
|||||||
|
|
||||||
use super::TextEdit;
|
use super::TextEdit;
|
||||||
use super::hit_test::{ single_line_align_offset, single_line_scroll_x };
|
use super::hit_test::{ single_line_align_offset, single_line_scroll_x };
|
||||||
use super::metrics::MultilineMetrics;
|
use super::metrics::{ single_line_pad_h, single_line_scale, MultilineMetrics };
|
||||||
use super::theme;
|
use super::theme;
|
||||||
use super::wrapping::compute_visual_lines;
|
use super::wrapping::compute_visual_lines;
|
||||||
|
|
||||||
@@ -47,15 +47,9 @@ fn rect_intersect( a: Rect, b: Rect ) -> Option<Rect>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The toggle follows the field height, which is already in physical pixels.
|
|
||||||
fn password_toggle_scale( rect: Rect ) -> f32
|
|
||||||
{
|
|
||||||
rect.height / theme::HEIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
fn password_toggle_reserve( rect: Rect ) -> f32
|
fn password_toggle_reserve( rect: Rect ) -> f32
|
||||||
{
|
{
|
||||||
( theme::PASSWORD_TOGGLE_SIZE + theme::PASSWORD_TOGGLE_SLOP * 2.0 ) * password_toggle_scale( rect )
|
( theme::PASSWORD_TOGGLE_SIZE + theme::PASSWORD_TOGGLE_SLOP * 2.0 ) * single_line_scale( rect )
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hit rect for the eye icon at the right edge of a `TextEdit`
|
/// Hit rect for the eye icon at the right edge of a `TextEdit`
|
||||||
@@ -67,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 )
|
||||||
+ theme::PAD_H * 0.5 * password_toggle_scale( rect ) ).min( rect.width );
|
+ single_line_pad_h( rect ) * 0.5 ).min( rect.width );
|
||||||
Rect
|
Rect
|
||||||
{
|
{
|
||||||
x: rect.x + rect.width - zone_w,
|
x: rect.x + rect.width - zone_w,
|
||||||
@@ -127,9 +121,10 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
{
|
{
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
|
let pad_h = single_line_pad_h( rect );
|
||||||
let text_rect = Rect
|
let text_rect = Rect
|
||||||
{
|
{
|
||||||
width: ( rect.width - toggle_reserve ).max( theme::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 );
|
||||||
@@ -143,9 +138,9 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
let outer_clip = canvas.clip_bounds();
|
let outer_clip = canvas.clip_bounds();
|
||||||
let inner_rect = Rect
|
let inner_rect = Rect
|
||||||
{
|
{
|
||||||
x: rect.x + theme::PAD_H * 0.5,
|
x: rect.x + pad_h * 0.5,
|
||||||
y: rect.y,
|
y: rect.y,
|
||||||
width: ( rect.width - theme::PAD_H - toggle_reserve ).max( 0.0 ),
|
width: ( rect.width - pad_h - toggle_reserve ).max( 0.0 ),
|
||||||
height: rect.height,
|
height: rect.height,
|
||||||
};
|
};
|
||||||
canvas.set_clip_rects( &interior_clip( &outer_clip, inner_rect ) );
|
canvas.set_clip_rects( &interior_clip( &outer_clip, inner_rect ) );
|
||||||
@@ -170,7 +165,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
} else {
|
} else {
|
||||||
self.value[s..e].to_string()
|
self.value[s..e].to_string()
|
||||||
};
|
};
|
||||||
let x0 = rect.x + theme::PAD_H + align_x - scroll_x
|
let x0 = rect.x + pad_h + align_x - scroll_x
|
||||||
+ canvas.measure_text( &prefix_text, font_size );
|
+ canvas.measure_text( &prefix_text, font_size );
|
||||||
let w = canvas.measure_text( &span_text, font_size );
|
let w = canvas.measure_text( &span_text, font_size );
|
||||||
let sel_rect = Rect
|
let sel_rect = Rect
|
||||||
@@ -191,7 +186,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
);
|
);
|
||||||
canvas.draw_text(
|
canvas.draw_text(
|
||||||
&self.placeholder,
|
&self.placeholder,
|
||||||
rect.x + theme::PAD_H + placeholder_align_x,
|
rect.x + pad_h + placeholder_align_x,
|
||||||
text_y,
|
text_y,
|
||||||
font_size,
|
font_size,
|
||||||
theme::placeholder(),
|
theme::placeholder(),
|
||||||
@@ -199,7 +194,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
} else {
|
} else {
|
||||||
canvas.draw_text(
|
canvas.draw_text(
|
||||||
&text,
|
&text,
|
||||||
rect.x + theme::PAD_H + align_x - scroll_x,
|
rect.x + pad_h + align_x - scroll_x,
|
||||||
text_y,
|
text_y,
|
||||||
font_size,
|
font_size,
|
||||||
theme::text(),
|
theme::text(),
|
||||||
@@ -215,7 +210,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
} else {
|
} else {
|
||||||
self.value[..safe_cursor].to_string()
|
self.value[..safe_cursor].to_string()
|
||||||
};
|
};
|
||||||
let cursor_x = rect.x + theme::PAD_H + align_x - scroll_x
|
let cursor_x = rect.x + pad_h + align_x - scroll_x
|
||||||
+ canvas.measure_text( &cursor_text, font_size );
|
+ canvas.measure_text( &cursor_text, font_size );
|
||||||
// Caret height follows the text line, not the field box — a
|
// Caret height follows the text line, not the field box — a
|
||||||
// tall field (launcher pill) otherwise grows a caret twice
|
// tall field (launcher pill) otherwise grows a caret twice
|
||||||
@@ -249,7 +244,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
|||||||
if let Some( ( visible, _ ) ) = self.password_toggle.as_ref()
|
if let Some( ( visible, _ ) ) = self.password_toggle.as_ref()
|
||||||
{
|
{
|
||||||
let icon_name = if *visible { "actions/invisible" } else { "actions/visible" };
|
let icon_name = if *visible { "actions/invisible" } else { "actions/visible" };
|
||||||
let icon_px = ( theme::PASSWORD_TOGGLE_SIZE * password_toggle_scale( rect ) ).round().max( 1.0 ) as u32;
|
let icon_px = ( theme::PASSWORD_TOGGLE_SIZE * single_line_scale( rect ) ).round().max( 1.0 ) as u32;
|
||||||
if let Some( ( rgba, iw, ih ) ) = crate::theme::icon_rgba( icon_name, icon_px )
|
if let Some( ( rgba, iw, ih ) ) = crate::theme::icon_rgba( icon_name, icon_px )
|
||||||
{
|
{
|
||||||
let tinted = crate::theme::tint_symbolic( &rgba, theme::placeholder() );
|
let tinted = crate::theme::tint_symbolic( &rgba, theme::placeholder() );
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
use crate::render::Canvas;
|
use crate::render::Canvas;
|
||||||
use crate::types::{ Length, Point, Rect };
|
use crate::types::{ Length, Point, Rect };
|
||||||
|
|
||||||
use super::metrics::MultilineMetrics;
|
use super::metrics::{ single_line_pad_h, MultilineMetrics };
|
||||||
use super::theme;
|
|
||||||
use super::wrapping::compute_visual_lines;
|
use super::wrapping::compute_visual_lines;
|
||||||
|
|
||||||
/// Convert a pointer position inside the widget rect to the byte offset
|
/// Convert a pointer position inside the widget rect to the byte offset
|
||||||
@@ -65,7 +64,7 @@ pub( crate ) fn byte_offset_at(
|
|||||||
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 );
|
||||||
byte_offset_in_line(
|
byte_offset_in_line(
|
||||||
canvas, value, 0, value.len(),
|
canvas, value, 0, value.len(),
|
||||||
pos.x - rect.x - theme::PAD_H - align_x + scroll_x,
|
pos.x - rect.x - single_line_pad_h( rect ) - align_x + scroll_x,
|
||||||
secure,
|
secure,
|
||||||
font_size,
|
font_size,
|
||||||
)
|
)
|
||||||
@@ -98,7 +97,7 @@ pub( crate ) fn single_line_scroll_x(
|
|||||||
font_size: f32,
|
font_size: f32,
|
||||||
) -> f32
|
) -> f32
|
||||||
{
|
{
|
||||||
let inner_width = ( rect.width - theme::PAD_H * 2.0 ).max( font_size );
|
let inner_width = ( rect.width - single_line_pad_h( rect ) * 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
|
||||||
@@ -147,7 +146,7 @@ pub( crate ) fn single_line_align_offset(
|
|||||||
{
|
{
|
||||||
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 - theme::PAD_H * 2.0 ).max( font_size );
|
let inner_width = ( rect.width - single_line_pad_h( rect ) * 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() )
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ use crate::types::{ Length, Rect };
|
|||||||
|
|
||||||
use super::theme;
|
use super::theme;
|
||||||
|
|
||||||
|
/// Single-line geometry follows the field height, which is already physical.
|
||||||
|
pub( crate ) fn single_line_scale( rect: Rect ) -> f32
|
||||||
|
{
|
||||||
|
rect.height / theme::HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
pub( crate ) fn single_line_pad_h( rect: Rect ) -> f32
|
||||||
|
{
|
||||||
|
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,
|
||||||
/// hit-testing and caret navigation so they all agree. `font_size` is in
|
/// hit-testing and caret navigation so they all agree. `font_size` is in
|
||||||
/// font space (pre-`dpi_scale`); every other field is physical px.
|
/// font space (pre-`dpi_scale`); every other field is physical px.
|
||||||
|
|||||||
Reference in New Issue
Block a user