2 Commits a90f9e65ef ... a4ce0c8939

Author SHA1 Message Date
  Kestrel a4ce0c8939 Change TextEdit to use pushed text input. 1 month ago
  Kestrel f54c24213f Correct display of text cursor at end of a text edit widget. 2 months ago
6 changed files with 105 additions and 55 deletions
  1. 1 1
      src/layout.rs
  2. 35 1
      src/render.rs
  3. 7 2
      src/render/text.rs
  4. 1 1
      src/ui.rs
  5. 50 48
      src/widget/text_edit.rs
  6. 11 2
      src/window.rs

+ 1 - 1
src/layout.rs

@@ -552,7 +552,7 @@ impl<'l> Iterator for LayoutChildIter<'l> {
 /// This trait accepts a tag type parameter to allow a single struct to store multiple layout nodes
 /// with internal edges. Use a `Tag` type of `()` or similar to denote a struct that only
 /// stores a single layout node.
-pub trait LayoutTreeNode<Tag: 'static> {
+pub trait LayoutTreeNode<Tag: 'static = ()> {
     fn current_node(&self) -> &LayoutNode;
     fn child_count(&self) -> usize;
     fn child(&self, ndx: usize) -> Option<LayoutNodeAccess<'_>>;

+ 35 - 1
src/render.rs

@@ -1,6 +1,6 @@
 use kahlo::{
     colour::Colour,
-    math::PixelBox,
+    math::{PixelBox, PixelSideOffsets},
     prelude::{BitmapAccess, KahloOps},
 };
 
@@ -95,6 +95,19 @@ impl<'l, 'r: 'l, 'data: 'r> NodeRenderTarget<'l, 'r, 'data> {
         self
     }
 
+    pub fn fill_relative_box(mut self, mut bx: PixelBox, cc: ColourChoice) -> Self {
+        if let Self::Render { rt, area, .. } = &mut self {
+            let shift = area.min.to_vector();
+            bx.min += shift;
+            bx.max += shift;
+            if let Some(region) = bx.intersection(area) {
+                rt.target
+                    .fill_region(region, cc.get(rt.theme, ColourChoice::Background));
+            }
+        }
+        self
+    }
+
     pub fn draw_foreground_outline(self) -> Self {
         self
     }
@@ -129,4 +142,25 @@ impl<'l, 'r: 'l, 'data: 'r> NodeRenderTarget<'l, 'r, 'data> {
         }
         self
     }
+
+    pub fn seq(self, f: impl FnOnce(NodeRenderTarget)) -> Self {
+        if let Self::Render { rt, area, lnode } = self {
+            f(NodeRenderTarget::Render { rt, area, lnode });
+            NodeRenderTarget::Render { rt, area, lnode }
+        } else {
+            self
+        }
+    }
+
+    pub fn shrink(self, by: PixelSideOffsets) -> Self {
+        if let Self::Render { rt, area, lnode } = self {
+            Self::Render {
+                rt,
+                lnode,
+                area: area.inner_box(by),
+            }
+        } else {
+            self
+        }
+    }
 }

+ 7 - 2
src/render/text.rs

@@ -11,7 +11,7 @@ pub struct TextCache {
     text: String,
     font: Rc<fontdue::Font>,
     pxsize: f32,
-    x_offsets: Vec<u32>,
+    pub(crate) x_offsets: RefCell<Vec<u32>>,
     rendered: RefCell<Option<kahlo::Alphamap>>,
 }
 
@@ -21,7 +21,7 @@ impl TextCache {
             text: String::new(),
             font,
             pxsize,
-            x_offsets: vec![],
+            x_offsets: vec![].into(),
             rendered: Default::default(),
         }
     }
@@ -122,6 +122,9 @@ impl TextCache {
         let mut alphamap =
             kahlo::Alphamap::new(alphamap_size.width as usize, alphamap_size.height as usize);
 
+        let mut x_offsets = self.x_offsets.borrow_mut();
+        x_offsets.clear();
+
         // pass: generate alphamap
         for (offset, _width, ch) in self.generate_baseline_offsets(pxsize, text) {
             let (metrics, raster) = self.font.rasterize(ch, pxsize);
@@ -131,6 +134,8 @@ impl TextCache {
                 metrics.height,
             );
 
+            x_offsets.push(offset);
+
             alphamap.copy_from(
                 &character_alphamap,
                 PixelBox::from_size(character_alphamap.size()),

+ 1 - 1
src/ui.rs

@@ -210,7 +210,7 @@ impl<UIC: UIComponent> winit::application::ApplicationHandler<()> for UI<UIC> {
                     wsa.request_redraw();
                 }
             }
-            _ => {}
+            _evt => {}
         }
     }
 }

+ 50 - 48
src/widget/text_edit.rs

@@ -1,10 +1,14 @@
 use crate::{
     input::MouseButton,
-    layout::{HorizontalAlignment, LayoutNode, LayoutNodeAccess, LayoutTreeNode, LeafLayoutNode, SizePolicy},
+    layout::{
+        HorizontalAlignment, LayoutNode, LayoutNodeAccess, LayoutTreeNode, LeafLayoutNode,
+        SizePolicy,
+    },
     prelude::*,
     render::{ColourChoice, RenderTarget, TextCache},
     ui::UIHandle,
 };
+use kahlo::math::{PixelBox, PixelPoint, PixelSideOffsets, PixelSize};
 use winit::keyboard::{Key, NamedKey};
 
 pub enum CursorPosition {
@@ -13,9 +17,12 @@ pub enum CursorPosition {
     End,
 }
 
+/*impl CursorPosition {
+
+}*/
+
 pub struct TextEdit<C: Component> {
-    lnode: LayoutNode,
-    cursor_node: LeafLayoutNode,
+    lnode: LeafLayoutNode,
     text: TextCache,
     cursor_pos: CursorPosition,
     onchange: Option<Box<dyn Fn(&str) -> Option<C::Msg>>>,
@@ -30,22 +37,15 @@ impl<C: Component> TextEdit<C> {
             .set_width_policy(SizePolicy::expanding(1))
             .set_height_policy(SizePolicy::fixed(minheight));
 
-        let mut cursor_node = uih.new_layout_node();
-        cursor_node
-            .set_width_policy(SizePolicy::fixed(2))
-            .set_height_policy(SizePolicy::fixed(minheight))
-            .set_halign(HorizontalAlignment::Left);
-
         Self {
-            lnode,
-            cursor_node: LeafLayoutNode::new(cursor_node),
+            lnode: LeafLayoutNode::new(lnode),
             text: TextCache::new_with_font_and_size(
                 uih.theme().ui_font.clone(),
                 uih.theme().ui_font_size,
             ),
-            cursor_pos: CursorPosition::End,
+            cursor_pos: CursorPosition::Start,
             onchange: None,
-            focused: false
+            focused: false,
         }
     }
 
@@ -54,24 +54,8 @@ impl<C: Component> TextEdit<C> {
     }
 
     /*pub fn set_cursor_position(&mut self, to: CursorPosition) {
-        
-    }*/
-}
 
-impl<C: Component> LayoutTreeNode<()> for TextEdit<C> {
-    fn child(&self, ndx: usize) -> Option<LayoutNodeAccess<'_>> {
-        if ndx == 0 {
-            Some(self.cursor_node.access())
-        } else {
-            None
-        }
-    }
-    fn child_count(&self) -> usize {
-        1
-    }
-    fn current_node(&self) -> &LayoutNode {
-        &self.lnode
-    }
+    }*/
 }
 
 impl<C: Component> Widget<C> for TextEdit<C> {
@@ -85,6 +69,7 @@ impl<C: Component> Widget<C> for TextEdit<C> {
         };
 
         if istate.is_focused(self.lnode.id()) {
+            let mut key_press_eaten = true;
             if let Some(kp) = &istate.keypress {
                 match kp {
                     Key::Named(NamedKey::Backspace) => {
@@ -95,36 +80,40 @@ impl<C: Component> Widget<C> for TextEdit<C> {
                         self.text.change(String::pop);
                         self.lnode.render_needed();
                     }
+                    Key::Named(NamedKey::Space) => {
+                        self.text.change(|v| v.push(' '));
+                        self.lnode.render_needed();
+                    }
                     Key::Named(NamedKey::Enter) => {
                         istate.clear_focus();
                     }
-                    _ => {
-                        if let Some(inp) = &istate.text_input {
-                            self.text.change(|s| s.push_str(inp.as_str()));
-                            self.lnode.render_needed();
-                        }
-                    }
+                    Key::Named(_) => {}
+                    _ => key_press_eaten = false,
+                }
+            }
+            if !key_press_eaten {
+                if let Some(text) = &istate.text_input {
+                    println!("adding text {text:?}");
+                    self.text.change(|s| s.push_str(text.as_str()));
+                    self.lnode.render_needed();
                 }
             }
         }
+
         let render_area = self.lnode.render_area().unwrap();
         if istate
             .mouse_status_in(render_area, MouseButton::Left)
             .is_click()
         {
             istate.focus_on(self.lnode.id());
-            self.focused = true;
             println!("click offset: {:?}", istate.mouse.pos - render_area.min);
-
-            let cbar_offset = self.text.size_hint().width;
-            self.cursor_node.margin_mut().left = cbar_offset;
         }
 
         let was_focused = self.focused;
         self.focused = istate.is_focused(self.lnode.id());
 
         if self.focused != was_focused {
-            self.cursor_node.render_needed();
+            println!("self.focused is now {}", self.focused);
             self.lnode.render_needed();
         }
 
@@ -132,7 +121,7 @@ impl<C: Component> Widget<C> for TextEdit<C> {
     }
 
     fn layout_node(&self) -> LayoutNodeAccess {
-        LayoutNodeAccess::new(self)
+        self.lnode.access()
     }
     fn layout_node_mut(&mut self) -> &mut LayoutNode {
         &mut self.lnode
@@ -142,11 +131,24 @@ impl<C: Component> Widget<C> for TextEdit<C> {
         target
             .with_node(&self.lnode)
             .fill(ColourChoice::Background)
-            .simple_text(&self.text, ColourChoice::Foreground);
-
-        if self.focused {
-            target.with_node(&self.cursor_node)
-                .fill(ColourChoice::Foreground);
-        }
+            .simple_text(&self.text, ColourChoice::Foreground)
+            .seq(|target| {
+                if self.focused {
+                    let cbar_offset = match self.cursor_pos {
+                        CursorPosition::Start => 0,
+                        CursorPosition::Index(idx) => {
+                            *self.text.x_offsets.borrow().get(idx).unwrap() as i32
+                        }
+                        CursorPosition::End => self.text.size_hint().width,
+                    };
+                    target.fill_relative_box(
+                        PixelBox::from_origin_and_size(
+                            PixelPoint::new(cbar_offset, 0),
+                            PixelSize::new(2, self.text.size_hint().height),
+                        ),
+                        ColourChoice::Foreground,
+                    );
+                }
+            });
     }
 }

+ 11 - 2
src/window.rs

@@ -167,6 +167,7 @@ pub(crate) trait WindowStateAccess {
     fn update_mouse_button(&self, which: MouseButton, to: bool);
     fn update_text_input(&self, what: &str);
     fn push_keypress(&self, key: winit::keyboard::Key);
+    fn push_text_input(&self, input: String);
 }
 
 impl<WC: WindowComponent> WindowStateAccess for RefCell<WindowState<WC>> {
@@ -211,15 +212,23 @@ impl<WC: WindowComponent> WindowStateAccess for RefCell<WindowState<WC>> {
 
         // special handling for the F12 key, which dumps the current layout tree
         if key == winit::keyboard::Key::Named(winit::keyboard::NamedKey::F12) {
-            let layout = WindowLayoutHelper::new(&win.root_node, win.wc.root_widget().layout_node());
+            let layout =
+                WindowLayoutHelper::new(&win.root_node, win.wc.root_widget().layout_node());
 
-            let mut tree_file = std::io::BufWriter::new(std::fs::File::create("patina-tree.json").unwrap());
+            let mut tree_file =
+                std::io::BufWriter::new(std::fs::File::create("patina-tree.json").unwrap());
 
             crate::layout::dump_tree_json(LayoutNodeAccess::new(&layout), &mut tree_file).unwrap();
         } else {
             is.keypress = Some(key);
         }
     }
+    fn push_text_input(&self, input: String) {
+        let mut win = self.borrow_mut();
+        let is = &mut win.istate;
+
+        is.set_text_input(input);
+    }
 }
 
 pub struct WindowBuilder<'r, 'l: 'r> {