diff --git a/crates/kas-view/src/driver.rs b/crates/kas-view/src/driver.rs index ae7fa0adb..c92720d6c 100644 --- a/crates/kas-view/src/driver.rs +++ b/crates/kas-view/src/driver.rs @@ -147,7 +147,7 @@ impl Driver for View { type Widget = CheckBox; fn make(&mut self, _: &Key) -> Self::Widget { - CheckBox::new(|_, data: &bool| *data).with_editable(false) + CheckBox::new(|_, data: &bool| *data).with_read_only(true) } fn set_key(&mut self, _: &mut Self::Widget, _: &Key) { // CheckBox has no metadata that needs to be reset diff --git a/crates/kas-widgets/src/check_box.rs b/crates/kas-widgets/src/check_box.rs index 235f86dad..a2404ae59 100644 --- a/crates/kas-widgets/src/check_box.rs +++ b/crates/kas-widgets/src/check_box.rs @@ -25,7 +25,7 @@ mod CheckBox { pub struct CheckBox { core: widget_core!(), state: bool, - editable: bool, + read_only: bool, last_change: Option, state_fn: Box bool + Send>, on_toggle: Option>, @@ -91,7 +91,7 @@ mod CheckBox { CheckBox { core: Default::default(), state: false, - editable: true, + read_only: false, last_change: None, state_fn: Box::new(state_fn), on_toggle: None, @@ -129,28 +129,32 @@ mod CheckBox { CheckBox::new(state_fn).with_msg(msg_fn) } - /// Set whether this widget is editable (inline) + /// Set whether this widget is read-only (inline) #[inline] #[must_use] - pub fn with_editable(mut self, editable: bool) -> Self { - self.editable = editable; + pub fn with_read_only(mut self, read_only: bool) -> Self { + self.read_only = read_only; self } - /// Get whether this widget is editable + /// Get whether this widget is read-only #[inline] - pub fn is_editable(&self) -> bool { - self.editable + pub fn is_read_only(&self) -> bool { + self.read_only } - /// Set whether this widget is editable + /// Set whether this widget is read-only #[inline] - pub fn set_editable(&mut self, editable: bool) { - self.editable = editable; + pub fn set_read_only(&mut self, read_only: bool) { + self.read_only = read_only; } - /// Toggle the check box + /// Toggle the check box, if not read-only pub fn toggle(&mut self, cx: &mut EventCx, data: &A) { + if self.read_only { + return; + } + // Note: do not update self.state; that is the responsibility of update. self.state = !self.state; if let Some(f) = self.on_toggle.as_ref() { @@ -298,24 +302,24 @@ mod CheckButton { CheckButton::new(label, state_fn).with_msg(msg_fn) } - /// Set whether this widget is editable (inline) + /// Set whether this widget is read-only (inline) #[inline] #[must_use] - pub fn editable(mut self, editable: bool) -> Self { - self.inner = self.inner.with_editable(editable); + pub fn read_only(mut self, read_only: bool) -> Self { + self.inner = self.inner.with_read_only(read_only); self } - /// Get whether this widget is editable + /// Get whether this widget is read-only #[inline] - pub fn is_editable(&self) -> bool { - self.inner.is_editable() + pub fn is_read_only(&self) -> bool { + self.inner.is_read_only() } - /// Set whether this widget is editable + /// Set whether this widget is read-only #[inline] - pub fn set_editable(&mut self, editable: bool) { - self.inner.set_editable(editable); + pub fn set_read_only(&mut self, read_only: bool) { + self.inner.set_read_only(read_only); } fn direction(&self) -> Direction { diff --git a/crates/kas-widgets/src/dialog.rs b/crates/kas-widgets/src/dialog.rs index 9ede22615..4d6cd8896 100644 --- a/crates/kas-widgets/src/dialog.rs +++ b/crates/kas-widgets/src/dialog.rs @@ -15,7 +15,7 @@ use crate::adapt::AdaptWidgetAny; use crate::edit::Editor; -use crate::{AccessLabel, Button, EditBox, Filler, ScrollLabel, SelectableLabel}; +use crate::{AccessLabel, Button, EditBox, Filler, ScrollLabel}; use kas::prelude::*; use kas::runner::AppData; use kas::text::format::FormattableText; @@ -33,7 +33,7 @@ mod MessageBox { pub struct MessageBox { core: widget_core!(), #[widget] - label: SelectableLabel, + label: ScrollLabel, #[widget] button: Button, } @@ -43,7 +43,7 @@ mod MessageBox { pub fn new(message: T) -> Self { MessageBox { core: Default::default(), - label: SelectableLabel::new(message), + label: ScrollLabel::new(message), button: Button::label_msg("&Ok", MessageBoxOk), } } @@ -101,7 +101,7 @@ mod AlertError { parent: Id, title: String, #[widget] - label: SelectableLabel, + label: ScrollLabel, #[widget] details: ScrollLabel, #[widget] @@ -122,7 +122,7 @@ mod AlertError { core: Default::default(), parent: Id::default(), title: "Error".to_string(), - label: SelectableLabel::new(message), + label: ScrollLabel::new(message), details: ScrollLabel::new(details), ok: Button::label_msg("&Ok", ErrorResult), } @@ -194,7 +194,7 @@ mod AlertUnsaved { parent: Id, title: String, #[widget] - label: SelectableLabel, + label: ScrollLabel, #[widget] save: Button, #[widget] @@ -210,7 +210,7 @@ mod AlertUnsaved { core: Default::default(), parent: Id::default(), title: "Unsaved changes".to_string(), - label: SelectableLabel::new(message), + label: ScrollLabel::new(message), save: Button::label_msg("&Save", UnsavedResult::Save), discard: Button::label_msg("&Discard", UnsavedResult::Discard), cancel: Button::label_msg("&Cancel", UnsavedResult::Cancel), diff --git a/crates/kas-widgets/src/edit/edit_box.rs b/crates/kas-widgets/src/edit/edit_box.rs index 8b0e6d3ed..b8dea6f94 100644 --- a/crates/kas-widgets/src/edit/edit_box.rs +++ b/crates/kas-widgets/src/edit/edit_box.rs @@ -3,14 +3,13 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -//! The [`EditField`] and [`EditBox`] widgets, plus supporting items +//! The [`EditBox`] widget use super::*; use crate::edit::highlight::{Highlighter, Plain}; use crate::{ScrollBar, ScrollBarMsg}; use kas::event::Scroll; use kas::event::components::ScrollComponent; -use kas::messages::{ReplaceSelectedText, SetValueText}; use kas::prelude::*; use kas::theme::{FrameStyle, TextClass}; use std::fmt::{Debug, Display}; @@ -21,17 +20,43 @@ mod EditBox { /// A text-edit box /// /// A single- or multi-line editor for unformatted text. - /// See also notes on [`EditField`]. /// /// By default, the editor supports a single-line only; /// [`Self::with_multi_line`] can be used to change this. /// + /// ### Event handling + /// + /// This widget attempts to handle all standard text-editor input and scroll + /// events. + /// + /// Key events for moving the edit cursor (e.g. arrow keys) are consumed + /// only if the edit cursor is moved while key events for adjusting or using + /// the selection (e.g. `Command::Copy` and `Command::Deselect`) + /// are consumed only when a selection exists. In contrast, key events for + /// inserting or deleting text are always consumed. + /// + /// [`Command::Enter`] inserts a line break in multi-line mode, but in + /// single-line mode or if the Shift key is held it is treated + /// the same as [`Command::Activate`]. + /// + /// ### Performance and limitations + /// + /// Text representation is via a single [`String`]. Edit operations are + /// `O(n)` where `n` is the length of text (with text layout algorithms + /// having greater cost than copying bytes in the backing [`String`]). + /// This isn't necessarily *slow*; when run with optimizations the type can + /// handle type-setting around 20kB of UTF-8 in under 10ms (with significant + /// scope for optimization, given that currently layout is re-run from + /// scratch on each key stroke). Regardless, this approach is not designed + /// to scale to handle large documents via a single `EditBox` widget. + /// /// ### Messages /// - /// [`SetValueText`] may be used to replace the entire text and - /// [`ReplaceSelectedText`] may be used to replace selected text when this - /// widget is [editable](Editor::is_editable). This triggers the action - /// handlers [`EditGuard::edit`] followed by [`EditGuard::activate`]. + /// [`kas::messages::SetValueText`] may be used to replace the entire text + /// and [`kas::messages::ReplaceSelectedText`] may be used to replace + /// selected text when this widget is not [read-only](Editor::is_read_only). + /// Both add an item to the undo history and invoke the action handler + /// [`EditGuard::edit`]. /// /// [`kas::messages::SetScrollOffset`] may be used to set the scroll offset. #[autoimpl(Debug where G: trait, H: trait)] @@ -42,9 +67,10 @@ mod EditBox { scroll: ScrollComponent, // NOTE: inner is a Viewport which doesn't use update methods, therefore we don't call them. #[widget] - inner: EditField, + inner: EditBoxCore, #[widget(&())] vert_bar: ScrollBar, + frame_style: FrameStyle, frame_offset: Offset, frame_size: Size, frame_offset_ex_margin: Offset, @@ -64,7 +90,7 @@ mod EditBox { rules.append(bar_rules); } - let frame_rules = cx.frame(FrameStyle::EditBox, axis); + let frame_rules = cx.frame(self.frame_style, axis); self.frame_offset_ex_margin .set_component(axis, frame_rules.size()); let (rules, offset, size) = frame_rules.surround(rules); @@ -111,7 +137,7 @@ mod EditBox { let mut draw_inner = draw.re(); draw_inner.set_id(self.inner.id()); let bg = self.inner.background_color(); - draw_inner.frame(self.rect(), FrameStyle::EditBox, bg); + draw_inner.frame(self.rect(), self.frame_style, bg); self.inner .draw_with_offset(draw.re(), self.clip_rect, self.scroll.offset()); @@ -170,29 +196,18 @@ mod EditBox { } fn handle_messages(&mut self, cx: &mut EventCx<'_>, data: &G::Data) { - let action = if cx.last_child() == Some(widget_index![self.vert_bar]) + let offset = if cx.last_child() == Some(widget_index![self.vert_bar]) && let Some(ScrollBarMsg(y)) = cx.try_pop() { - let offset = Offset(self.scroll.offset().0, y); - self.scroll.set_offset(offset) + Offset(self.scroll.offset().0, y) } else if let Some(kas::messages::SetScrollOffset(offset)) = cx.try_pop() { - self.scroll.set_offset(offset) - } else if self.is_editable() - && let Some(SetValueText(string)) = cx.try_pop() - { - self.edit(cx, data, |edit, cx| { - edit.pre_commit(); - edit.set_string(cx, string); - }); - return; - } else if let Some(&ReplaceSelectedText(_)) = cx.try_peek() { - self.inner.handle_messages(cx, data); - return; + offset } else { + self.inner.handle_messages(cx, data); return; }; - if let Some(moved) = action { + if let Some(moved) = self.scroll.set_offset(offset) { cx.action_moved(moved); self.update_scroll_offset(cx); } @@ -232,8 +247,9 @@ mod EditBox { EditBox { core: Default::default(), scroll: Default::default(), - inner: EditField::new(guard), + inner: EditBoxCore::new(guard), vert_bar: Default::default(), + frame_style: FrameStyle::EditBox, frame_offset: Default::default(), frame_size: Default::default(), frame_offset_ex_margin: Default::default(), @@ -254,6 +270,7 @@ mod EditBox { scroll: self.scroll, inner: self.inner.with_highlighter(highlighter), vert_bar: self.vert_bar, + frame_style: self.frame_style, frame_offset: self.frame_offset, frame_size: self.frame_size, frame_offset_ex_margin: self.frame_offset_ex_margin, @@ -267,6 +284,15 @@ mod EditBox { self.inner.set_highlighter(highlighter); } + /// Replace the frame style + /// + /// The default is [`FrameStyle::EditBox`]. + #[inline] + pub fn with_frame_style(mut self, style: FrameStyle) -> Self { + self.frame_style = style; + self + } + fn update_content_size(&mut self, cx: &mut EventState) { if !self.core.status.is_sized() { return; @@ -301,7 +327,7 @@ impl EditBox> { #[inline] pub fn text(text: S) -> Self { EditBox { - inner: EditField::text(text), + inner: EditBoxCore::text(text), ..Default::default() } } @@ -309,7 +335,7 @@ impl EditBox> { /// Construct a read-only `EditBox` displaying some `String` value #[inline] pub fn string(value_fn: impl Fn(&A) -> String + Send + 'static) -> EditBox> { - EditBox::new(StringGuard::new(value_fn)).with_editable(false) + EditBox::new(StringGuard::new(value_fn)).with_read_only(true) } /// Construct an `EditBox` for a parsable value (e.g. a number) @@ -356,14 +382,14 @@ impl EditBox> { /// The `msg_fn` is called when the field is activated (Enter) /// and when it loses focus after content is changed. /// - /// This method sets self as editable (see [`Self::with_editable`]). + /// This method sets self as editable (see [`Self::with_read_only`]). #[must_use] pub fn with_msg(mut self, msg_fn: impl Fn(&str) -> M + Send + 'static) -> Self where M: Debug + 'static, { self.inner.guard = self.inner.guard.with_msg(msg_fn); - self.inner = self.inner.with_editable(true); + self.inner = self.inner.with_read_only(false); self } } @@ -379,11 +405,11 @@ impl EditBox { self } - /// Set whether this widget is editable (inline) + /// Set whether this `EditBox` is read-only (inline) #[inline] #[must_use] - pub fn with_editable(mut self, editable: bool) -> Self { - self.inner = self.inner.with_editable(editable); + pub fn with_read_only(mut self, read_only: bool) -> Self { + self.inner = self.inner.with_read_only(read_only); self } diff --git a/crates/kas-widgets/src/edit/edit_field.rs b/crates/kas-widgets/src/edit/edit_field.rs index 1094a5eea..a3c022c05 100644 --- a/crates/kas-widgets/src/edit/edit_field.rs +++ b/crates/kas-widgets/src/edit/edit_field.rs @@ -3,7 +3,7 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -//! The [`EditField`] and [`EditBox`] widgets, plus supporting items +//! The [`EditBoxCore`] widget use super::*; use crate::edit::highlight::{Highlighter, Plain}; @@ -11,16 +11,14 @@ use kas::event::CursorIcon; use kas::messages::{ReplaceSelectedText, SetValueText}; use kas::prelude::*; use kas::theme::{Background, TextClass}; -use std::fmt::{Debug, Display}; use std::ops::Deref; -use std::str::FromStr; #[impl_self] -mod EditField { +mod EditBoxCore { /// A text-edit field (single- or multi-line) /// - /// The [`EditBox`] widget should be preferred in most cases; this widget - /// is a component of `EditBox` and has some special behaviour. + /// The [`EditBox`] widget should be preferred in almost all cases; this + /// widget is a component of [`EditBox`] and has some special behaviour. /// /// By default, the editor supports a single-line only; /// [`Self::with_multi_line`] can be used to change this. @@ -49,14 +47,14 @@ mod EditField { /// handle type-setting around 20kB of UTF-8 in under 10ms (with significant /// scope for optimization, given that currently layout is re-run from /// scratch on each key stroke). Regardless, this approach is not designed - /// to scale to handle large documents via a single `EditField` widget. + /// to scale to handle large documents via a single `EditBoxCore` widget. /// /// ### Messages /// /// [`SetValueText`] may be used to replace the entire text and /// [`ReplaceSelectedText`] may be used to replace selected text when this - /// widget is [editable](Editor::is_editable)]. This triggers the action - /// handlers [`EditGuard::edit`] followed by [`EditGuard::activate`]. + /// widget is not [read-only](Editor::is_read_only). Both add an item to + /// the undo history and invoke the action handler [`EditGuard::edit`]. /// /// ### Special behaviour /// @@ -64,7 +62,7 @@ mod EditField { #[autoimpl(Debug where G: trait, H: trait)] #[widget] #[layout(self.editor)] - pub struct EditField, H: Highlighter = Plain> { + pub struct EditBoxCore, H: Highlighter = Plain> { core: widget_core!(), width: (f32, f32), lines: (f32, f32), @@ -206,7 +204,7 @@ mod EditField { } fn handle_messages(&mut self, cx: &mut EventCx, data: &G::Data) { - if !self.is_editable() { + if self.is_read_only() { return; } @@ -224,21 +222,21 @@ mod EditField { } } - impl Default for EditField + impl Default for EditBoxCore where G: Default, { #[inline] fn default() -> Self { - EditField::new(G::default()) + EditBoxCore::new(G::default()) } } - impl EditField { + impl EditBoxCore { /// Construct an `EditBox` with an [`EditGuard`] #[inline] - pub fn new(guard: G) -> EditField { - EditField { + pub fn new(guard: G) -> EditBoxCore { + EditBoxCore { core: Default::default(), width: (8.0, 16.0), lines: (1.0, 1.0), @@ -253,8 +251,8 @@ mod EditField { /// /// This function reconstructs the text with a new highlighter. #[inline] - pub fn with_highlighter(self, highlighter: H2) -> EditField { - EditField { + pub fn with_highlighter(self, highlighter: H2) -> EditBoxCore { + EditBoxCore { core: self.core, width: self.width, lines: self.lines, @@ -294,81 +292,21 @@ mod EditField { } } -impl EditField> { - /// Construct an `EditField` with the given inital `text` (no event handling) +impl EditBoxCore> { + /// Construct an `EditBoxCore` with the given inital `text` (no event handling) #[inline] pub fn text(text: S) -> Self { - EditField { + EditBoxCore { editor: Component::from(text), ..Default::default() } } - - /// Construct a read-only `EditField` displaying some `String` value - #[inline] - pub fn string(value_fn: impl Fn(&A) -> String + Send + 'static) -> EditField> { - EditField::new(StringGuard::new(value_fn)).with_editable(false) - } - - /// Construct an `EditField` for a parsable value (e.g. a number) - /// - /// On update, `value_fn` is used to extract a value from input data - /// which is then formatted as a string via [`Display`]. - /// If, however, the input field has focus, the update is ignored. - /// - /// On every edit, the guard attempts to parse the field's input as type - /// `T` via [`FromStr`], caching the result and setting the error state. - /// - /// On field activation and focus loss when a `T` value is cached (see - /// previous paragraph), `on_afl` is used to construct a message to be - /// emitted via [`EventCx::push`]. The cached value is then cleared to - /// avoid sending duplicate messages. - #[inline] - pub fn parser( - value_fn: impl Fn(&A) -> T + Send + 'static, - msg_fn: impl Fn(T) -> M + Send + 'static, - ) -> EditField> { - EditField::new(ParseGuard::new(value_fn, msg_fn)) - } - - /// Construct an `EditField` for a parsable value (e.g. a number) - /// - /// On update, `value_fn` is used to extract a value from input data - /// which is then formatted as a string via [`Display`]. - /// If, however, the input field has focus, the update is ignored. - /// - /// On every edit, the guard attempts to parse the field's input as type - /// `T` via [`FromStr`]. On success, the result is converted to a - /// message via `on_afl` then emitted via [`EventCx::push`]. - pub fn instant_parser( - value_fn: impl Fn(&A) -> T + Send + 'static, - msg_fn: impl Fn(T) -> M + Send + 'static, - ) -> EditField> { - EditField::new(InstantParseGuard::new(value_fn, msg_fn)) - } -} - -impl EditField> { - /// Assign a message function for a `String` value - /// - /// The `msg_fn` is called when the field is activated (Enter) - /// and when it loses focus after content is changed. - /// - /// This method sets self as editable (see [`Self::with_editable`]). - #[must_use] - pub fn with_msg(mut self, msg_fn: impl Fn(&str) -> M + Send + 'static) -> Self - where - M: Debug + 'static, - { - self.guard = self.guard.with_msg(msg_fn); - self.with_editable(true) - } } -impl EditField { +impl EditBoxCore { /// Set the initial text (inline) /// - /// This method should only be used on a new `EditField`. + /// This method should only be used on a new `EditBoxCore`. #[inline] #[must_use] pub fn with_text(mut self, text: impl ToString) -> Self { @@ -376,15 +314,15 @@ impl EditField { self } - /// Set whether this `EditField` is editable (inline) + /// Set whether this `EditBoxCore` is read-only (inline) #[inline] #[must_use] - pub fn with_editable(mut self, editable: bool) -> Self { - self.editor.0.set_editable(editable); + pub fn with_read_only(mut self, read_only: bool) -> Self { + self.editor.0.set_read_only(read_only); self } - /// Set whether this `EditField` uses multi-line mode + /// Set whether this `EditBoxCore` uses multi-line mode /// /// This affects the (vertical) size allocation, alignment, text wrapping /// and whether the Enter key may instert a line break. diff --git a/crates/kas-widgets/src/edit/editor.rs b/crates/kas-widgets/src/edit/editor.rs index 8550dd142..5089789d1 100644 --- a/crates/kas-widgets/src/edit/editor.rs +++ b/crates/kas-widgets/src/edit/editor.rs @@ -28,7 +28,7 @@ use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation}; /// Inner editor component /// /// This type is made public for use as the associated `Target` type of the -/// [`Deref`](std::ops::Deref) impl on `EditField` and `EditBox`. It will no +/// [`Deref`](std::ops::Deref) impl on `EditBoxCore` and `EditBox`. It will no /// longer be needed once `impl trait` is stabilised for associated types. /// (Alternatively, [`Editor`] could be re-implemented on the above widgets; /// this is preferable in theory but requires a lot of tedious code.) @@ -36,7 +36,7 @@ use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation}; pub struct Editor { // TODO(opt): id is duplicated here since macros don't let us put the core here id: Id, - editable: bool, + read_only: bool, display: ConfiguredDisplay, text: String, colors: SchemeColors, @@ -50,10 +50,44 @@ pub struct Editor { input_handler: TextInput, } +impl Default for Editor { + #[inline] + fn default() -> Self { + Editor { + id: Id::default(), + read_only: false, + display: ConfiguredDisplay::new(TextClass::Editor, false), + text: Default::default(), + colors: SchemeColors::default(), + selection: Default::default(), + edit_x_coord: None, + last_edit: Some(EditOp::Initial), + undo_stack: UndoStack::new(), + has_key_focus: false, + current: CurrentAction::None, + error_state: None, + input_handler: Default::default(), + } + } +} + +impl From for Editor { + #[inline] + fn from(text: S) -> Self { + let text = text.to_string(); + let len = text.len(); + Editor { + text, + selection: SelectionHelper::from(len), + ..Self::default() + } + } +} + /// Editor component /// /// This is a component used to implement an editor widget. It is used, for -/// example, in [`EditField`]. +/// example, in [`EditBoxCore`]. /// /// ### Special behaviour /// @@ -111,38 +145,14 @@ impl Layout for Component { impl Default for Component { #[inline] fn default() -> Self { - let editor = Editor { - id: Id::default(), - editable: true, - display: ConfiguredDisplay::new(TextClass::Editor, false), - text: Default::default(), - colors: SchemeColors::default(), - selection: Default::default(), - edit_x_coord: None, - last_edit: Some(EditOp::Initial), - undo_stack: UndoStack::new(), - has_key_focus: false, - current: CurrentAction::None, - error_state: None, - input_handler: Default::default(), - }; - - Component(editor, Default::default()) + Component(Editor::default(), Default::default()) } } impl From for Component { #[inline] fn from(text: S) -> Self { - let text = text.to_string(); - let len = text.len(); - let editor = Editor { - text, - selection: SelectionHelper::from(len), - ..Self::default().0 - }; - - Component(editor, highlight::Text::new(H::default())) + Component(Editor::from(text), Default::default()) } } @@ -406,7 +416,7 @@ impl Component { draw.decorate_text(pos, rect, display, &tokens[r0..]); } - if self.0.editable && draw.ev_state().has_input_focus(self.0.id_ref()) == Some(true) { + if !self.0.read_only && draw.ev_state().has_input_focus(self.0.id_ref()) == Some(true) { draw.text_cursor( pos, rect, @@ -884,7 +894,7 @@ impl Editor { /// /// Committing undo state is the responsibility of the caller. fn received_text(&mut self, cx: &mut EventCx, text: &str) -> IsUsed { - if !self.editable { + if self.read_only { return Unused; } self.cancel_selection_and_ime(cx); @@ -934,7 +944,7 @@ impl Editor { mut cmd: Command, code: Option, ) -> Result { - let editable = self.editable; + let editable = !self.read_only; let mut shift = cx.modifiers().shift_key(); let mut buf = [0u8; 4]; let cursor = self.selection.edit_index(); @@ -1391,16 +1401,16 @@ impl Editor { self.selection = range.into(); } - /// Get whether this `EditField` is editable + /// Get whether this text-edit widget is read-only #[inline] - pub fn is_editable(&self) -> bool { - self.editable + pub fn is_read_only(&self) -> bool { + self.read_only } - /// Set whether this `EditField` is editable + /// Set whether this text-edit widget is editable #[inline] - pub fn set_editable(&mut self, editable: bool) { - self.editable = editable; + pub fn set_read_only(&mut self, read_only: bool) { + self.read_only = read_only; } /// True if the editor uses multi-line mode diff --git a/crates/kas-widgets/src/edit/guard.rs b/crates/kas-widgets/src/edit/guard.rs index 700cfbb0f..ddc16cbfa 100644 --- a/crates/kas-widgets/src/edit/guard.rs +++ b/crates/kas-widgets/src/edit/guard.rs @@ -13,13 +13,12 @@ use std::str::FromStr; /// Event-handling *guard* for an [`Editor`] /// -/// This is the most generic interface; see also constructors of [`EditField`], -/// [`EditBox`] for common use-cases. +/// This is the most generic interface; see also [`EditBox`] constructors for +/// common use-cases. /// /// All methods have a default implementation which does nothing. /// /// [`EditBox`]: super::EditBox -/// [`EditField`]: super::EditField pub trait EditGuard: Sized { /// Data type type Data; @@ -54,7 +53,7 @@ pub trait EditGuard: Sized { /// returns [`Used`]. /// - If the field is not editable, returns [`Unused`]. fn activate(&mut self, edit: &mut Editor, cx: &mut EventCx, data: &Self::Data) -> IsUsed { - if edit.is_editable() { + if !edit.is_read_only() { self.focus_lost(edit, cx, data); Used } else { diff --git a/crates/kas-widgets/src/edit/mod.rs b/crates/kas-widgets/src/edit/mod.rs index 1273b69d1..a385fa834 100644 --- a/crates/kas-widgets/src/edit/mod.rs +++ b/crates/kas-widgets/src/edit/mod.rs @@ -3,7 +3,7 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -//! The [`EditField`] and [`EditBox`] widgets, plus supporting items +//! The [`EditBoxCore`] and [`EditBox`] widgets, plus supporting items mod edit_box; mod edit_field; @@ -12,7 +12,7 @@ mod guard; pub mod highlight; pub use edit_box::EditBox; -pub use edit_field::EditField; +pub use edit_field::EditBoxCore; pub use editor::{Component, Editor}; pub use guard::*; diff --git a/crates/kas-widgets/src/lib.rs b/crates/kas-widgets/src/lib.rs index 5a0f5f534..91cf32690 100644 --- a/crates/kas-widgets/src/lib.rs +++ b/crates/kas-widgets/src/lib.rs @@ -16,7 +16,7 @@ //! - [`adapt`] provides [`Adapt`], [`AdaptWidget`], [`AdaptWidgetAny`] and supporting items //! (the items mentioned are re-export here). //! - [`dialog`] provides [`MessageBox`](dialog::MessageBox), ... -//! - [`edit`] provides text-editing functionality; the [`EditBox`] and [`EditField`] widgets are re-export here +//! - [`edit`] provides text-editing functionality; the [`EditBox`] and [`EditBoxCore`] widgets are re-export here //! - [`menu`] provides a [`MenuBar`](menu::MenuBar), [`SubMenu`](menu::SubMenu), ... //! //! ## Container widgets @@ -97,7 +97,7 @@ pub use access_label::AccessLabel; pub use button::Button; pub use check_box::{CheckBox, CheckButton}; pub use combobox::ComboBox; -pub use edit::{EditBox, EditField}; +pub use edit::{EditBox, EditBoxCore}; pub use event_config::EventConfig; pub use filler::Filler; pub use float::Float; @@ -110,7 +110,7 @@ pub use progress::ProgressBar; pub use radio_box::{RadioBox, RadioButton}; pub use scroll::{ClipRegion, ScrollRegion}; pub use scroll_bar::{ScrollBar, ScrollBarMode, ScrollBarMsg}; -pub use scroll_label::{ScrollLabel, ScrollText, SelectableLabel, SelectableText}; +pub use scroll_label::{ScrollLabel, ScrollText, ScrollTextCore}; pub use separator::Separator; pub use slider::{Slider, SliderValue}; pub use spin_box::{SpinBox, SpinValue}; diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index e88779a5b..d3f2cf0f5 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -14,7 +14,7 @@ use kas::text::{SelectionHelper, Text}; use kas::theme::TextClass; #[impl_self] -mod SelectableText { +mod ScrollTextCore { /// A text label supporting selection /// /// The [`ScrollText`] widget should be preferred in most cases; this widget @@ -31,7 +31,7 @@ mod SelectableText { /// This is a [`Viewport`] widget. #[widget] #[layout(self.text)] - pub struct SelectableText { + pub struct ScrollTextCore { core: widget_core!(), text: Text, text_fn: Option T + Send>>, @@ -100,13 +100,13 @@ mod SelectableText { } } - impl SelectableText<(), T> { - /// Construct a `SelectableText` with the given inital `text` + impl ScrollTextCore<(), T> { + /// Construct a `ScrollTextCore` with the given inital `text` /// /// The text is set from input data on update. #[inline] pub fn new(text: T) -> Self { - SelectableText { + ScrollTextCore { core: Default::default(), text: Text::new(text, TextClass::Standard, true), text_fn: None, @@ -123,8 +123,8 @@ mod SelectableText { pub fn with_fn( self, text_fn: impl Fn(&ConfigCx, &A) -> T + Send + 'static, - ) -> SelectableText { - SelectableText { + ) -> ScrollTextCore { + ScrollTextCore { core: self.core, text: self.text, text_fn: Some(Box::new(text_fn)), @@ -136,7 +136,7 @@ mod SelectableText { } impl Self { - /// Construct an `SelectableText` with the given text derivation function + /// Construct an `ScrollTextCore` with the given text derivation function /// /// The text is set from input data on update. #[inline] @@ -144,7 +144,7 @@ mod SelectableText { where T: Default, { - SelectableText::<(), T>::new(T::default()).with_fn(text_fn) + ScrollTextCore::<(), T>::new(T::default()).with_fn(text_fn) } /// Get text contents @@ -218,7 +218,7 @@ mod SelectableText { /// Update view_offset from `cursor` /// - /// This method is mostly identical to its counterpart in `EditField`. + /// This method is mostly identical to its counterpart in `Editor`. fn set_view_offset_from_cursor(&mut self, cx: &mut EventCx, cursor: usize) { if let Some(marker) = self .text @@ -329,22 +329,19 @@ mod SelectableText { } } -/// A text label supporting selection -/// -/// Line-wrapping is enabled; default alignment is derived from the script -/// (usually top-left). -pub type SelectableLabel = SelectableText<(), T>; - #[impl_self] mod ScrollText { /// A text label supporting scrolling and selection /// - /// This widget is a wrapper around [`SelectableText`] enabling scrolling - /// and adding a vertical scroll bar. + /// This is a read-only text object supporting scrolling and text selection. + /// Scrolling support and a vertical scroll-bar is enabled automatically as + /// required. + /// + /// Text contents may be fixed (see [`Self::new`]), dynamic from input data + /// ([`Self::new_fn`]) or assigned ([`Self::set_text`]). /// /// By default, this uses [`TextClass::Standard`]; see [`Self::set_class`] /// and [`Self::with_class`]. - /// /// Line-wrapping is enabled; default alignment is derived from the script /// (usually top-left). /// @@ -357,7 +354,7 @@ mod ScrollText { scroll: ScrollComponent, // NOTE: text is a Viewport which doesn't use update methods, therefore we don't call them. #[widget] - text: SelectableText, + text: ScrollTextCore, #[widget = &()] vert_bar: ScrollBar, } @@ -428,7 +425,7 @@ mod ScrollText { ScrollText { core: Default::default(), scroll: Default::default(), - text: SelectableText::new(text), + text: ScrollTextCore::new(text), vert_bar: ScrollBar::new().with_invisible(true), } } @@ -571,9 +568,5 @@ mod ScrollText { /// A text label supporting scrolling and selection /// -/// This widget is a wrapper around [`SelectableText`] enabling scrolling -/// and adding a vertical scroll bar. -/// -/// Line-wrapping is enabled; default alignment is derived from the script -/// (usually top-left). +/// This is a variant of [`ScrollText`] for usage with fixed text contents only. pub type ScrollLabel = ScrollText<(), T>; diff --git a/crates/kas-widgets/src/spin_box.rs b/crates/kas-widgets/src/spin_box.rs index 219892289..e24337b39 100644 --- a/crates/kas-widgets/src/spin_box.rs +++ b/crates/kas-widgets/src/spin_box.rs @@ -7,7 +7,7 @@ use crate::{ MarkButton, - edit::{EditField, EditGuard, Editor}, + edit::{EditBoxCore, EditGuard, Editor}, }; use kas::messages::{DecrementStep, IncrementStep, ReplaceSelectedText, SetValueF64, SetValueText}; use kas::prelude::*; @@ -219,7 +219,7 @@ mod SpinBox { pub struct SpinBox { core: widget_core!(), #[widget] - edit: EditField>, + edit: EditBoxCore>, unit: Text, #[widget(&())] b_up: MarkButton, @@ -240,7 +240,7 @@ mod SpinBox { ) -> Self { SpinBox { core: Default::default(), - edit: EditField::new(SpinGuard::new(range, Box::new(state_fn))) + edit: EditBoxCore::new(SpinGuard::new(range, Box::new(state_fn))) .with_width_em(3.0, 8.0), unit: Text::new("".to_string(), TextClass::Label, false), b_up: MarkButton::new_msg( diff --git a/examples/splitter.rs b/examples/splitter.rs index 853476263..db602f6ee 100644 --- a/examples/splitter.rs +++ b/examples/splitter.rs @@ -6,7 +6,7 @@ //! Counter example (simple button) use kas::prelude::*; -use kas::widgets::{Adapt, Button, EditField, Splitter, column, row}; +use kas::widgets::{Adapt, Button, EditBox, Splitter, column, row}; #[cfg(feature = "wgpu")] type Theme = kas_wgpu::ShadedTheme; @@ -29,7 +29,9 @@ fn main() -> kas::runner::Result<()> { ] .map_any(), Splitter::right(vec![]).on_update(|cx, panes, len| panes.resize_with(len, cx, *len, |n| { - EditField::text(format!("Pane {}", n + 1)).with_multi_line(true) + EditBox::text(format!("Pane {}", n + 1)) + .with_frame_style(kas::theme::FrameStyle::None) + .with_multi_line(true) })), ];