diff --git a/src/event_loop/focus.rs b/src/event_loop/focus.rs index adc4ceb..0dda36f 100644 --- a/src/event_loop/focus.rs +++ b/src/event_loop/focus.rs @@ -119,28 +119,30 @@ impl AppData } ); ss.request_redraw(); - // Sync cursor to end of current value when focusing a text - // input. Default behaviour collapses the selection to the - // cursor — focus changes always discard any prior - // selection state. Fields built with `.select_on_focus( - // true )` instead anchor the selection at `0` so the - // whole value is highlighted, ready to be replaced by the - // next keystroke (typical for numeric pickers). + // Sync cursor to the end of the value when focusing a text + // input, via the `usize::MAX` sentinel ("end of whatever + // value is rendered" — every consumer clamps with + // `cursor.min( value.len() )`). A concrete `value.len()` + // snapshot here would go stale if the value keeps growing + // after focus (e.g. updated externally over IPC), leaving + // later insertions mid-string. Default behaviour collapses + // the selection to the cursor — focus changes always + // discard any prior selection state. Fields built with + // `.select_on_focus( true )` instead anchor the selection + // at `0` so the whole value is highlighted, ready to be + // replaced by the next keystroke (typical for numeric + // pickers). if is_text_input { if let Some( i ) = idx { let handler = find_handlers( &ss.frame.widget_rects, i ); - let cursor = handler - .and_then( |h| h.current_value() ) - .map( |v| v.len() ) - .unwrap_or( 0 ); let anchor = match handler { Some( WidgetHandlers::TextEdit { select_on_focus: true, .. } ) => 0, - _ => cursor, + _ => usize::MAX, }; - ss.frame.cursor_state.insert( i, cursor ); + ss.frame.cursor_state.insert( i, usize::MAX ); ss.frame.selection_anchor.insert( i, anchor ); } } diff --git a/src/layout/row.rs b/src/layout/row.rs index 48e97ac..295bc36 100644 --- a/src/layout/row.rs +++ b/src/layout/row.rs @@ -47,6 +47,8 @@ pub struct Row /// Padding on all sides. [`Length`]; default `0.0` px. pub( crate ) padding: Length, pub( crate ) align_right: bool, + pub( crate ) align_top: bool, + pub( crate ) fill_height: bool, } impl Row @@ -59,6 +61,8 @@ impl Row spacing: Length::px( 8.0 ), padding: Length::px( 0.0 ), align_right: false, + align_top: false, + fill_height: false, } } @@ -104,6 +108,26 @@ impl Row self } + /// Pin children to the top edge instead of the default vertical + /// centering, so siblings of slightly different heights (e.g. two + /// cards whose text metrics differ) share the same top line. + pub fn align_top( mut self ) -> Self + { + self.align_top = true; + self + } + + /// Stretch every child to the row's inner height, so siblings share + /// both the top and the bottom line regardless of their natural + /// heights. The row's own height still comes from the tallest + /// child's preferred size; children whose content should track the + /// stretch need internal flexible spacers. + pub fn fill_height( mut self ) -> Self + { + self.fill_height = true; + self + } + /// Return the preferred `(width, height)` given available `max_width`. pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32) { @@ -240,8 +264,15 @@ impl Row Element::Flex( f ) => flex_unit * f.weight as f32, _ => w, }; - let y = rect.y + pad + ( inner_h - h ) / 2.0; - result.push( ( Rect { x, y, width, height: h }, i ) ); + let ( y, height ) = if self.fill_height && !matches!( child, Element::Spacer( _ ) ) + { + ( rect.y + pad, inner_h ) + } else if self.align_top { + ( rect.y + pad, h ) + } else { + ( rect.y + pad + ( inner_h - h ) / 2.0, h ) + }; + result.push( ( Rect { x, y, width, height }, i ) ); x += width + spacing; } result @@ -258,6 +289,8 @@ impl Row spacing: self.spacing, padding: self.padding, align_right: self.align_right, + align_top: self.align_top, + fill_height: self.fill_height, } } } diff --git a/src/widget/text_edit/draw.rs b/src/widget/text_edit/draw.rs index ad918e0..1230147 100644 --- a/src/widget/text_edit/draw.rs +++ b/src/widget/text_edit/draw.rs @@ -195,12 +195,16 @@ impl TextEdit }; let cursor_x = rect.x + theme::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 + // the glyph height. Matches the multiline caret. + let cursor_h = font_size + 4.0; let cursor_rect = Rect { x: cursor_x, - y: rect.y + 8.0, + y: rect.y + ( rect.height - cursor_h ) / 2.0, width: 2.0, - height: rect.height - 16.0, + height: cursor_h, }; canvas.fill_rect( cursor_rect, theme::cursor(), 0.0 ); }