diff --git a/src/widget/text_edit/draw.rs b/src/widget/text_edit/draw.rs index c31ade8..27e352c 100644 --- a/src/widget/text_edit/draw.rs +++ b/src/widget/text_edit/draw.rs @@ -6,7 +6,7 @@ use crate::types::Rect; use super::TextEdit; 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::wrapping::compute_visual_lines; @@ -47,15 +47,9 @@ fn rect_intersect( a: Rect, b: Rect ) -> Option } } -// 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 { - ( 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` @@ -67,7 +61,7 @@ fn password_toggle_reserve( rect: Rect ) -> f32 pub fn password_toggle_hit_zone( rect: Rect ) -> 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 { x: rect.x + rect.width - zone_w, @@ -127,9 +121,10 @@ impl TextEdit { 0.0 }; + let pad_h = single_line_pad_h( 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 }; let scroll_x = single_line_scroll_x( canvas, text_rect, &self.value, cursor_pos, secure, font_size ); @@ -143,9 +138,9 @@ impl TextEdit let outer_clip = canvas.clip_bounds(); let inner_rect = Rect { - x: rect.x + theme::PAD_H * 0.5, + x: rect.x + pad_h * 0.5, 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, }; canvas.set_clip_rects( &interior_clip( &outer_clip, inner_rect ) ); @@ -170,7 +165,7 @@ impl TextEdit } else { 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 ); let w = canvas.measure_text( &span_text, font_size ); let sel_rect = Rect @@ -191,7 +186,7 @@ impl TextEdit ); canvas.draw_text( &self.placeholder, - rect.x + theme::PAD_H + placeholder_align_x, + rect.x + pad_h + placeholder_align_x, text_y, font_size, theme::placeholder(), @@ -199,7 +194,7 @@ impl TextEdit } else { canvas.draw_text( &text, - rect.x + theme::PAD_H + align_x - scroll_x, + rect.x + pad_h + align_x - scroll_x, text_y, font_size, theme::text(), @@ -215,7 +210,7 @@ impl TextEdit } else { 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 ); // Caret height follows the text line, not the field box — a // tall field (launcher pill) otherwise grows a caret twice @@ -249,7 +244,7 @@ impl TextEdit if let Some( ( visible, _ ) ) = self.password_toggle.as_ref() { 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 ) { let tinted = crate::theme::tint_symbolic( &rgba, theme::placeholder() ); diff --git a/src/widget/text_edit/hit_test.rs b/src/widget/text_edit/hit_test.rs index de705ea..e5ea0d8 100644 --- a/src/widget/text_edit/hit_test.rs +++ b/src/widget/text_edit/hit_test.rs @@ -4,8 +4,7 @@ use crate::render::Canvas; use crate::types::{ Length, Point, Rect }; -use super::metrics::MultilineMetrics; -use super::theme; +use super::metrics::{ single_line_pad_h, MultilineMetrics }; use super::wrapping::compute_visual_lines; /// 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 ); byte_offset_in_line( 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, font_size, ) @@ -98,7 +97,7 @@ pub( crate ) fn single_line_scroll_x( font_size: 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 display_value: String = if secure @@ -147,7 +146,7 @@ pub( crate ) fn single_line_align_offset( { use crate::widget::text::TextAlign; 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 { "\u{2022}".repeat( value.chars().count() ) diff --git a/src/widget/text_edit/metrics.rs b/src/widget/text_edit/metrics.rs index 0b611ee..8a9ff68 100644 --- a/src/widget/text_edit/metrics.rs +++ b/src/widget/text_edit/metrics.rs @@ -6,6 +6,17 @@ use crate::types::{ Length, Rect }; 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, /// hit-testing and caret navigation so they all agree. `font_size` is in /// font space (pre-`dpi_scale`); every other field is physical px.