Skip to content

Commit 82df4e6

Browse files
authored
Merge pull request #683 from kas-gui/push-qnnuwksvonsw
SelectableText → ScrollTextCore; EditField → EditBoxCore
2 parents ab005e2 + 92aa597 commit 82df4e6

File tree

12 files changed

+200
-228
lines changed

12 files changed

+200
-228
lines changed

crates/kas-view/src/driver.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ impl<Key> Driver<Key, bool> for View {
147147
type Widget = CheckBox<bool>;
148148

149149
fn make(&mut self, _: &Key) -> Self::Widget {
150-
CheckBox::new(|_, data: &bool| *data).with_editable(false)
150+
CheckBox::new(|_, data: &bool| *data).with_read_only(true)
151151
}
152152
fn set_key(&mut self, _: &mut Self::Widget, _: &Key) {
153153
// CheckBox has no metadata that needs to be reset

crates/kas-widgets/src/check_box.rs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ mod CheckBox {
2525
pub struct CheckBox<A> {
2626
core: widget_core!(),
2727
state: bool,
28-
editable: bool,
28+
read_only: bool,
2929
last_change: Option<Instant>,
3030
state_fn: Box<dyn Fn(&ConfigCx, &A) -> bool + Send>,
3131
on_toggle: Option<Box<dyn Fn(&mut EventCx, &A, bool) + Send>>,
@@ -91,7 +91,7 @@ mod CheckBox {
9191
CheckBox {
9292
core: Default::default(),
9393
state: false,
94-
editable: true,
94+
read_only: false,
9595
last_change: None,
9696
state_fn: Box::new(state_fn),
9797
on_toggle: None,
@@ -129,28 +129,32 @@ mod CheckBox {
129129
CheckBox::new(state_fn).with_msg(msg_fn)
130130
}
131131

132-
/// Set whether this widget is editable (inline)
132+
/// Set whether this widget is read-only (inline)
133133
#[inline]
134134
#[must_use]
135-
pub fn with_editable(mut self, editable: bool) -> Self {
136-
self.editable = editable;
135+
pub fn with_read_only(mut self, read_only: bool) -> Self {
136+
self.read_only = read_only;
137137
self
138138
}
139139

140-
/// Get whether this widget is editable
140+
/// Get whether this widget is read-only
141141
#[inline]
142-
pub fn is_editable(&self) -> bool {
143-
self.editable
142+
pub fn is_read_only(&self) -> bool {
143+
self.read_only
144144
}
145145

146-
/// Set whether this widget is editable
146+
/// Set whether this widget is read-only
147147
#[inline]
148-
pub fn set_editable(&mut self, editable: bool) {
149-
self.editable = editable;
148+
pub fn set_read_only(&mut self, read_only: bool) {
149+
self.read_only = read_only;
150150
}
151151

152-
/// Toggle the check box
152+
/// Toggle the check box, if not read-only
153153
pub fn toggle(&mut self, cx: &mut EventCx, data: &A) {
154+
if self.read_only {
155+
return;
156+
}
157+
154158
// Note: do not update self.state; that is the responsibility of update.
155159
self.state = !self.state;
156160
if let Some(f) = self.on_toggle.as_ref() {
@@ -298,24 +302,24 @@ mod CheckButton {
298302
CheckButton::new(label, state_fn).with_msg(msg_fn)
299303
}
300304

301-
/// Set whether this widget is editable (inline)
305+
/// Set whether this widget is read-only (inline)
302306
#[inline]
303307
#[must_use]
304-
pub fn editable(mut self, editable: bool) -> Self {
305-
self.inner = self.inner.with_editable(editable);
308+
pub fn read_only(mut self, read_only: bool) -> Self {
309+
self.inner = self.inner.with_read_only(read_only);
306310
self
307311
}
308312

309-
/// Get whether this widget is editable
313+
/// Get whether this widget is read-only
310314
#[inline]
311-
pub fn is_editable(&self) -> bool {
312-
self.inner.is_editable()
315+
pub fn is_read_only(&self) -> bool {
316+
self.inner.is_read_only()
313317
}
314318

315-
/// Set whether this widget is editable
319+
/// Set whether this widget is read-only
316320
#[inline]
317-
pub fn set_editable(&mut self, editable: bool) {
318-
self.inner.set_editable(editable);
321+
pub fn set_read_only(&mut self, read_only: bool) {
322+
self.inner.set_read_only(read_only);
319323
}
320324

321325
fn direction(&self) -> Direction {

crates/kas-widgets/src/dialog.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
1616
use crate::adapt::AdaptWidgetAny;
1717
use crate::edit::Editor;
18-
use crate::{AccessLabel, Button, EditBox, Filler, ScrollLabel, SelectableLabel};
18+
use crate::{AccessLabel, Button, EditBox, Filler, ScrollLabel};
1919
use kas::prelude::*;
2020
use kas::runner::AppData;
2121
use kas::text::format::FormattableText;
@@ -33,7 +33,7 @@ mod MessageBox {
3333
pub struct MessageBox<T: FormattableText + 'static> {
3434
core: widget_core!(),
3535
#[widget]
36-
label: SelectableLabel<T>,
36+
label: ScrollLabel<T>,
3737
#[widget]
3838
button: Button<AccessLabel>,
3939
}
@@ -43,7 +43,7 @@ mod MessageBox {
4343
pub fn new(message: T) -> Self {
4444
MessageBox {
4545
core: Default::default(),
46-
label: SelectableLabel::new(message),
46+
label: ScrollLabel::new(message),
4747
button: Button::label_msg("&Ok", MessageBoxOk),
4848
}
4949
}
@@ -101,7 +101,7 @@ mod AlertError {
101101
parent: Id,
102102
title: String,
103103
#[widget]
104-
label: SelectableLabel<T>,
104+
label: ScrollLabel<T>,
105105
#[widget]
106106
details: ScrollLabel<String>,
107107
#[widget]
@@ -122,7 +122,7 @@ mod AlertError {
122122
core: Default::default(),
123123
parent: Id::default(),
124124
title: "Error".to_string(),
125-
label: SelectableLabel::new(message),
125+
label: ScrollLabel::new(message),
126126
details: ScrollLabel::new(details),
127127
ok: Button::label_msg("&Ok", ErrorResult),
128128
}
@@ -194,7 +194,7 @@ mod AlertUnsaved {
194194
parent: Id,
195195
title: String,
196196
#[widget]
197-
label: SelectableLabel<T>,
197+
label: ScrollLabel<T>,
198198
#[widget]
199199
save: Button<AccessLabel>,
200200
#[widget]
@@ -210,7 +210,7 @@ mod AlertUnsaved {
210210
core: Default::default(),
211211
parent: Id::default(),
212212
title: "Unsaved changes".to_string(),
213-
label: SelectableLabel::new(message),
213+
label: ScrollLabel::new(message),
214214
save: Button::label_msg("&Save", UnsavedResult::Save),
215215
discard: Button::label_msg("&Discard", UnsavedResult::Discard),
216216
cancel: Button::label_msg("&Cancel", UnsavedResult::Cancel),

crates/kas-widgets/src/edit/edit_box.rs

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
// You may obtain a copy of the License in the LICENSE-APACHE file or at:
44
// https://www.apache.org/licenses/LICENSE-2.0
55

6-
//! The [`EditField`] and [`EditBox`] widgets, plus supporting items
6+
//! The [`EditBox`] widget
77
88
use super::*;
99
use crate::edit::highlight::{Highlighter, Plain};
1010
use crate::{ScrollBar, ScrollBarMsg};
1111
use kas::event::Scroll;
1212
use kas::event::components::ScrollComponent;
13-
use kas::messages::{ReplaceSelectedText, SetValueText};
1413
use kas::prelude::*;
1514
use kas::theme::{FrameStyle, TextClass};
1615
use std::fmt::{Debug, Display};
@@ -21,17 +20,43 @@ mod EditBox {
2120
/// A text-edit box
2221
///
2322
/// A single- or multi-line editor for unformatted text.
24-
/// See also notes on [`EditField`].
2523
///
2624
/// By default, the editor supports a single-line only;
2725
/// [`Self::with_multi_line`] can be used to change this.
2826
///
27+
/// ### Event handling
28+
///
29+
/// This widget attempts to handle all standard text-editor input and scroll
30+
/// events.
31+
///
32+
/// Key events for moving the edit cursor (e.g. arrow keys) are consumed
33+
/// only if the edit cursor is moved while key events for adjusting or using
34+
/// the selection (e.g. `Command::Copy` and `Command::Deselect`)
35+
/// are consumed only when a selection exists. In contrast, key events for
36+
/// inserting or deleting text are always consumed.
37+
///
38+
/// [`Command::Enter`] inserts a line break in multi-line mode, but in
39+
/// single-line mode or if the <kbd>Shift</kbd> key is held it is treated
40+
/// the same as [`Command::Activate`].
41+
///
42+
/// ### Performance and limitations
43+
///
44+
/// Text representation is via a single [`String`]. Edit operations are
45+
/// `O(n)` where `n` is the length of text (with text layout algorithms
46+
/// having greater cost than copying bytes in the backing [`String`]).
47+
/// This isn't necessarily *slow*; when run with optimizations the type can
48+
/// handle type-setting around 20kB of UTF-8 in under 10ms (with significant
49+
/// scope for optimization, given that currently layout is re-run from
50+
/// scratch on each key stroke). Regardless, this approach is not designed
51+
/// to scale to handle large documents via a single `EditBox` widget.
52+
///
2953
/// ### Messages
3054
///
31-
/// [`SetValueText`] may be used to replace the entire text and
32-
/// [`ReplaceSelectedText`] may be used to replace selected text when this
33-
/// widget is [editable](Editor::is_editable). This triggers the action
34-
/// handlers [`EditGuard::edit`] followed by [`EditGuard::activate`].
55+
/// [`kas::messages::SetValueText`] may be used to replace the entire text
56+
/// and [`kas::messages::ReplaceSelectedText`] may be used to replace
57+
/// selected text when this widget is not [read-only](Editor::is_read_only).
58+
/// Both add an item to the undo history and invoke the action handler
59+
/// [`EditGuard::edit`].
3560
///
3661
/// [`kas::messages::SetScrollOffset`] may be used to set the scroll offset.
3762
#[autoimpl(Debug where G: trait, H: trait)]
@@ -42,9 +67,10 @@ mod EditBox {
4267
scroll: ScrollComponent,
4368
// NOTE: inner is a Viewport which doesn't use update methods, therefore we don't call them.
4469
#[widget]
45-
inner: EditField<G, H>,
70+
inner: EditBoxCore<G, H>,
4671
#[widget(&())]
4772
vert_bar: ScrollBar<kas::dir::Down>,
73+
frame_style: FrameStyle,
4874
frame_offset: Offset,
4975
frame_size: Size,
5076
frame_offset_ex_margin: Offset,
@@ -64,7 +90,7 @@ mod EditBox {
6490
rules.append(bar_rules);
6591
}
6692

67-
let frame_rules = cx.frame(FrameStyle::EditBox, axis);
93+
let frame_rules = cx.frame(self.frame_style, axis);
6894
self.frame_offset_ex_margin
6995
.set_component(axis, frame_rules.size());
7096
let (rules, offset, size) = frame_rules.surround(rules);
@@ -111,7 +137,7 @@ mod EditBox {
111137
let mut draw_inner = draw.re();
112138
draw_inner.set_id(self.inner.id());
113139
let bg = self.inner.background_color();
114-
draw_inner.frame(self.rect(), FrameStyle::EditBox, bg);
140+
draw_inner.frame(self.rect(), self.frame_style, bg);
115141

116142
self.inner
117143
.draw_with_offset(draw.re(), self.clip_rect, self.scroll.offset());
@@ -170,29 +196,18 @@ mod EditBox {
170196
}
171197

172198
fn handle_messages(&mut self, cx: &mut EventCx<'_>, data: &G::Data) {
173-
let action = if cx.last_child() == Some(widget_index![self.vert_bar])
199+
let offset = if cx.last_child() == Some(widget_index![self.vert_bar])
174200
&& let Some(ScrollBarMsg(y)) = cx.try_pop()
175201
{
176-
let offset = Offset(self.scroll.offset().0, y);
177-
self.scroll.set_offset(offset)
202+
Offset(self.scroll.offset().0, y)
178203
} else if let Some(kas::messages::SetScrollOffset(offset)) = cx.try_pop() {
179-
self.scroll.set_offset(offset)
180-
} else if self.is_editable()
181-
&& let Some(SetValueText(string)) = cx.try_pop()
182-
{
183-
self.edit(cx, data, |edit, cx| {
184-
edit.pre_commit();
185-
edit.set_string(cx, string);
186-
});
187-
return;
188-
} else if let Some(&ReplaceSelectedText(_)) = cx.try_peek() {
189-
self.inner.handle_messages(cx, data);
190-
return;
204+
offset
191205
} else {
206+
self.inner.handle_messages(cx, data);
192207
return;
193208
};
194209

195-
if let Some(moved) = action {
210+
if let Some(moved) = self.scroll.set_offset(offset) {
196211
cx.action_moved(moved);
197212
self.update_scroll_offset(cx);
198213
}
@@ -232,8 +247,9 @@ mod EditBox {
232247
EditBox {
233248
core: Default::default(),
234249
scroll: Default::default(),
235-
inner: EditField::new(guard),
250+
inner: EditBoxCore::new(guard),
236251
vert_bar: Default::default(),
252+
frame_style: FrameStyle::EditBox,
237253
frame_offset: Default::default(),
238254
frame_size: Default::default(),
239255
frame_offset_ex_margin: Default::default(),
@@ -254,6 +270,7 @@ mod EditBox {
254270
scroll: self.scroll,
255271
inner: self.inner.with_highlighter(highlighter),
256272
vert_bar: self.vert_bar,
273+
frame_style: self.frame_style,
257274
frame_offset: self.frame_offset,
258275
frame_size: self.frame_size,
259276
frame_offset_ex_margin: self.frame_offset_ex_margin,
@@ -267,6 +284,15 @@ mod EditBox {
267284
self.inner.set_highlighter(highlighter);
268285
}
269286

287+
/// Replace the frame style
288+
///
289+
/// The default is [`FrameStyle::EditBox`].
290+
#[inline]
291+
pub fn with_frame_style(mut self, style: FrameStyle) -> Self {
292+
self.frame_style = style;
293+
self
294+
}
295+
270296
fn update_content_size(&mut self, cx: &mut EventState) {
271297
if !self.core.status.is_sized() {
272298
return;
@@ -301,15 +327,15 @@ impl<A: 'static> EditBox<DefaultGuard<A>> {
301327
#[inline]
302328
pub fn text<S: ToString>(text: S) -> Self {
303329
EditBox {
304-
inner: EditField::text(text),
330+
inner: EditBoxCore::text(text),
305331
..Default::default()
306332
}
307333
}
308334

309335
/// Construct a read-only `EditBox` displaying some `String` value
310336
#[inline]
311337
pub fn string(value_fn: impl Fn(&A) -> String + Send + 'static) -> EditBox<StringGuard<A>> {
312-
EditBox::new(StringGuard::new(value_fn)).with_editable(false)
338+
EditBox::new(StringGuard::new(value_fn)).with_read_only(true)
313339
}
314340

315341
/// Construct an `EditBox` for a parsable value (e.g. a number)
@@ -356,14 +382,14 @@ impl<A: 'static> EditBox<StringGuard<A>> {
356382
/// The `msg_fn` is called when the field is activated (<kbd>Enter</kbd>)
357383
/// and when it loses focus after content is changed.
358384
///
359-
/// This method sets self as editable (see [`Self::with_editable`]).
385+
/// This method sets self as editable (see [`Self::with_read_only`]).
360386
#[must_use]
361387
pub fn with_msg<M>(mut self, msg_fn: impl Fn(&str) -> M + Send + 'static) -> Self
362388
where
363389
M: Debug + 'static,
364390
{
365391
self.inner.guard = self.inner.guard.with_msg(msg_fn);
366-
self.inner = self.inner.with_editable(true);
392+
self.inner = self.inner.with_read_only(false);
367393
self
368394
}
369395
}
@@ -379,11 +405,11 @@ impl<G: EditGuard, H: Highlighter> EditBox<G, H> {
379405
self
380406
}
381407

382-
/// Set whether this widget is editable (inline)
408+
/// Set whether this `EditBox` is read-only (inline)
383409
#[inline]
384410
#[must_use]
385-
pub fn with_editable(mut self, editable: bool) -> Self {
386-
self.inner = self.inner.with_editable(editable);
411+
pub fn with_read_only(mut self, read_only: bool) -> Self {
412+
self.inner = self.inner.with_read_only(read_only);
387413
self
388414
}
389415

0 commit comments

Comments
 (0)