Skip to content

Commit 8318879

Browse files
authored
Merge pull request #23 from kas-gui/push-lykwkxlnpnkm
Update kas to 0.17
2 parents 5853e25 + 55da750 commit 8318879

File tree

7 files changed

+113
-79
lines changed

7 files changed

+113
-79
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ jobs:
2323
override: true
2424
components: rustfmt
2525
- name: Install dependencies
26-
run: sudo apt-get install -y libxkbcommon-dev libxcb-shape0-dev libxcb-xfixes0-dev
26+
run: |
27+
sudo apt-get update
28+
sudo apt-get install -y libfontconfig1-dev
2729
- name: Test
2830
run: |
2931
cargo fmt -- --check

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ resolver = "2"
77
publish = false
88

99
[dependencies]
10-
kas = "0.16.0"
10+
kas = { version = "0.17.0", features = ["view"] }
1111
chrono = "0.4"
1212
env_logger = "0.11.8"
1313
pest = "2.1"

src/cells.rs

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55

66
//! Cells: a mini spreadsheet
77
8-
use kas::view::{
9-
DataChanges, DataClerk, DataKey, DataLen, Driver, GridIndex, GridView, TokenChanges,
10-
};
11-
use kas::widgets::{EditBox, EditField, EditGuard, ScrollBars};
8+
use kas::view::clerk::{self, AsyncClerk, TokenChanges, TokenClerk};
9+
use kas::view::{Driver, GridIndex, GridView};
10+
use kas::widgets::{edit, EditBox, ScrollRegion};
1211
use kas::{prelude::*, TextOrSource};
1312
use std::collections::HashMap;
1413
use std::fmt;
@@ -41,7 +40,7 @@ const ROW_LEN: u32 = 100;
4140

4241
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
4342
pub struct Key(ColKey, u8);
44-
impl DataKey for Key {
43+
impl clerk::Key for Key {
4544
fn make_id(&self, parent: &Id) -> Id {
4645
assert_eq!(std::mem::size_of::<ColKey>(), 1);
4746
let key = (((self.0).0 as usize) << 8) | (self.1 as usize);
@@ -338,28 +337,34 @@ struct Clerk {
338337
empty_cell: Cell,
339338
}
340339

341-
impl DataClerk<GridIndex> for Clerk {
340+
impl clerk::Clerk<GridIndex> for Clerk {
342341
type Data = CellData;
343-
type Key = Key;
344342
type Item = Cell;
345-
type Token = Key;
343+
344+
fn len(&self, _: &CellData, _: GridIndex) -> clerk::Len<GridIndex> {
345+
clerk::Len::Known(GridIndex {
346+
col: ColKey::LEN.cast(),
347+
row: ROW_LEN,
348+
})
349+
}
350+
}
351+
352+
impl AsyncClerk<GridIndex> for Clerk {
353+
type Key = Key;
346354

347355
fn update(
348356
&mut self,
349357
_: &mut ConfigCx<'_>,
350358
_: Id,
351359
_: Range<GridIndex>,
352360
_: &Self::Data,
353-
) -> DataChanges<GridIndex> {
354-
DataChanges::Any
361+
) -> clerk::Changes<GridIndex> {
362+
clerk::Changes::Any
355363
}
364+
}
356365

357-
fn len(&self, _: &CellData, _: GridIndex) -> DataLen<GridIndex> {
358-
DataLen::Known(GridIndex {
359-
col: ColKey::LEN.cast(),
360-
row: ROW_LEN,
361-
})
362-
}
366+
impl TokenClerk<GridIndex> for Clerk {
367+
type Token = Key;
363368

364369
fn update_token(
365370
&self,
@@ -395,35 +400,36 @@ struct CellGuard {
395400
key: Key,
396401
is_input: bool,
397402
}
398-
impl EditGuard for CellGuard {
403+
impl edit::EditGuard for CellGuard {
399404
type Data = Cell;
400405

401-
fn update(edit: &mut EditField<Self>, cx: &mut ConfigCx, item: &Cell) {
402-
edit.set_error_state(cx, item.parse_error);
403-
if !edit.has_edit_focus() {
404-
let text = if !item.display.is_empty() {
405-
&item.display
406-
} else {
407-
&item.input
408-
};
409-
edit.set_str(cx, text);
410-
edit.guard.is_input = false;
406+
fn update(&mut self, edit: &mut edit::Editor, cx: &mut ConfigCx, item: &Cell) {
407+
if item.parse_error {
408+
edit.set_error(cx, None);
411409
}
410+
411+
let text = if !item.display.is_empty() {
412+
&item.display
413+
} else {
414+
&item.input
415+
};
416+
edit.set_str(cx, text);
417+
self.is_input = false;
412418
}
413419

414-
fn activate(edit: &mut EditField<Self>, cx: &mut EventCx, item: &Cell) -> IsUsed {
415-
Self::focus_lost(edit, cx, item);
420+
fn activate(&mut self, edit: &mut edit::Editor, cx: &mut EventCx, item: &Cell) -> IsUsed {
421+
self.focus_lost(edit, cx, item);
416422
IsUsed::Used
417423
}
418424

419-
fn focus_gained(edit: &mut EditField<Self>, cx: &mut EventCx, item: &Cell) {
425+
fn focus_gained(&mut self, edit: &mut edit::Editor, cx: &mut EventCx, item: &Cell) {
420426
edit.set_str(cx, &item.input);
421-
edit.guard.is_input = true;
427+
self.is_input = true;
422428
}
423429

424-
fn focus_lost(edit: &mut EditField<Self>, cx: &mut EventCx, item: &Cell) {
425-
if edit.guard.is_input && edit.as_str() != item.input {
426-
cx.push(UpdateInput(edit.guard.key, edit.clone_string()));
430+
fn focus_lost(&mut self, edit: &mut edit::Editor, cx: &mut EventCx, item: &Cell) {
431+
if self.is_input && edit.as_str() != item.input {
432+
cx.push(UpdateInput(self.key, edit.clone_string()));
427433
}
428434
}
429435
}
@@ -436,6 +442,7 @@ impl Driver<Key, Cell> for CellDriver {
436442
// (a) there is currently no code to draw separators between cells
437443
// (b) EditField relies on a parent (EditBox) to draw background highlight on error state
438444
type Widget = EditBox<CellGuard>;
445+
const TAB_NAVIGABLE: bool = false;
439446

440447
fn make(&mut self, key: &Key) -> Self::Widget {
441448
EditBox::new(CellGuard {
@@ -479,8 +486,8 @@ pub fn window() -> Window<()> {
479486
struct {
480487
core: widget_core!(),
481488
data: CellData = data,
482-
#[widget(&self.data)] cells: ScrollBars<GridView<Clerk, CellDriver>> =
483-
ScrollBars::new(cells),
489+
#[widget(&self.data)] cells: ScrollRegion<GridView<Clerk, CellDriver>> =
490+
ScrollRegion::new_viewport(cells),
484491
}
485492
impl Events for Self {
486493
type Data = ();

src/crud.rs

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
use std::ops::Range;
99

1010
use kas::dir::Down;
11+
use kas::view::clerk::{AsyncClerk, Changes, Clerk, Len, TokenChanges, TokenClerk};
1112
use kas::view::filter::{ContainsCaseInsensitive, Filter, FilterValue, KeystrokeGuard, SetFilter};
12-
use kas::view::{DataChanges, DataClerk, DataLen, Driver, ListView, SelectionMsg, TokenChanges};
13-
use kas::widgets::edit::{EditBox, EditField, EditGuard};
14-
use kas::widgets::{AccessLabel, Button, ScrollBars, Text};
13+
use kas::view::{Driver, ListView, SelectionMsg};
14+
use kas::widgets::{edit, AccessLabel, Button, EditBox, ScrollRegion, Text};
1515
use kas::{prelude::*, TextOrSource};
1616

1717
#[derive(Clone, Debug)]
@@ -48,18 +48,24 @@ enum Control {
4848
struct NameGuard {
4949
is_last: bool,
5050
}
51-
impl EditGuard for NameGuard {
51+
impl edit::EditGuard for NameGuard {
5252
type Data = Option<Entry>;
5353

54-
fn update(edit: &mut EditField<Self>, cx: &mut ConfigCx, data: &Self::Data) {
54+
fn update(&mut self, edit: &mut edit::Editor, cx: &mut ConfigCx, data: &Self::Data) {
5555
if let Some(entry) = data.as_ref() {
56-
let name = match edit.guard.is_last {
56+
let name = match self.is_last {
5757
false => &entry.first,
5858
true => &entry.last,
5959
};
6060
edit.set_str(cx, name);
6161
}
62-
edit.set_error_state(cx, edit.as_str().is_empty());
62+
if edit.as_str().is_empty() {
63+
edit.set_error(cx, None);
64+
}
65+
}
66+
67+
fn focus_lost(&mut self, _: &mut edit::Editor, _: &mut EventCx<'_>, _: &Self::Data) {
68+
// Do nothing (the default impl calls Self::update)
6369
}
6470
}
6571

@@ -124,20 +130,39 @@ struct EntriesClerk {
124130
filtered_entries: Vec<usize>,
125131
}
126132

127-
impl DataClerk<usize> for EntriesClerk {
133+
impl EntriesClerk {
134+
/// Delete the entry at `index`; return an index of a nearby item
135+
fn delete(&mut self, index: usize) -> usize {
136+
self.entries[index] = None;
137+
for i in (index + 1..self.entries.len()).chain((0..index).rev()) {
138+
if self.entries[i].is_some() {
139+
return i;
140+
}
141+
}
142+
0
143+
}
144+
}
145+
146+
impl Clerk<usize> for EntriesClerk {
128147
type Data = ContainsCaseInsensitive;
129-
type Key = usize;
130148
type Item = Entry;
131-
type Token = usize;
149+
150+
fn len(&self, _: &Self::Data, _: usize) -> Len<usize> {
151+
Len::Known(self.filtered_entries.len())
152+
}
153+
}
154+
155+
impl AsyncClerk<usize> for EntriesClerk {
156+
type Key = usize;
132157

133158
fn update(
134159
&mut self,
135160
_: &mut ConfigCx,
136161
_: Id,
137162
_: Range<usize>,
138163
filter: &Self::Data,
139-
) -> DataChanges<usize> {
140-
// TODO(opt) determine when updates are a no-op and return DataChanges::None
164+
) -> Changes<usize> {
165+
// TODO(opt) determine when updates are a no-op and return Changes::None
141166

142167
self.filtered_entries = self
143168
.entries
@@ -151,12 +176,12 @@ impl DataClerk<usize> for EntriesClerk {
151176
.map(|(i, _)| i)
152177
.collect();
153178

154-
DataChanges::Any
179+
Changes::Any
155180
}
181+
}
156182

157-
fn len(&self, _: &Self::Data, _: usize) -> DataLen<usize> {
158-
DataLen::Known(self.filtered_entries.len())
159-
}
183+
impl TokenClerk<usize> for EntriesClerk {
184+
type Token = usize;
160185

161186
fn update_token(
162187
&self,
@@ -187,9 +212,10 @@ pub fn window() -> Window<()> {
187212
struct EntriesDriver;
188213
impl Driver<usize, Entry> for EntriesDriver {
189214
type Widget = Text<Entry, String>;
215+
const TAB_NAVIGABLE: bool = false;
190216

191217
fn make(&mut self, _: &usize) -> Self::Widget {
192-
Text::new(Entry::format)
218+
Text::new_gen(Entry::format)
193219
}
194220

195221
fn navigable(_: &Self::Widget) -> bool {
@@ -216,15 +242,15 @@ pub fn window() -> Window<()> {
216242
#[layout(grid! {
217243
(0, 0) => "Filter:",
218244
(1, 0) => self.filter_field,
219-
(0..2, 1..3) => frame!(self.list),
245+
(0..=1, 1..=2) => frame!(self.list),
220246
(3, 1) => self.editor,
221-
(0..4, 3) => self.controls,
247+
(0..=3, 3) => self.controls,
222248
})]
223249
struct {
224250
core: widget_core!(),
225251
#[widget(&())] filter_field: EditBox<KeystrokeGuard> = EditBox::new(KeystrokeGuard),
226-
#[widget(&self.filter)] list: ScrollBars<EntriesView> =
227-
ScrollBars::new(EntriesView::new(clerk, EntriesDriver).with_selection_mode(kas::view::SelectionMode::Single)),
252+
#[widget(&self.filter)] list: ScrollRegion<EntriesView> =
253+
ScrollRegion::new_viewport(EntriesView::new(clerk, EntriesDriver).with_selection_mode(kas::view::SelectionMode::Single)),
228254
#[widget(&self.selected)] editor: Editor = Editor::default(),
229255
#[widget(&self.selected)] controls: Controls = Controls::default(),
230256
filter: ContainsCaseInsensitive,
@@ -251,7 +277,6 @@ pub fn window() -> Window<()> {
251277
if let Some(item) = self.editor.make_item() {
252278
let index = self.list.inner().clerk().entries.len();
253279
self.list.inner_mut().clerk_mut().entries.push(Some(item));
254-
cx.update(self.list.as_node(&self.filter));
255280
self.list.inner_mut().select(cx, index);
256281
self.selected = self.list.inner().clerk().entries.get(index).cloned().flatten();
257282
cx.update(self.as_node(&()));
@@ -261,15 +286,13 @@ pub fn window() -> Window<()> {
261286
if let Some(index) = self.selected() {
262287
if let Some(item) = self.editor.make_item() {
263288
self.list.inner_mut().clerk_mut().entries[index] = Some(item);
264-
cx.update(self.list.as_node(&self.filter));
265289
cx.update(self.as_node(&()));
266290
}
267291
}
268292
}
269293
Control::Delete => {
270294
if let Some(index) = self.selected() {
271-
self.list.inner_mut().clerk_mut().entries[index] = None;
272-
cx.update(self.list.as_node(&self.filter));
295+
let index = self.list.inner_mut().clerk_mut().delete(index);
273296
self.list.inner_mut().select(cx, index);
274297
self.selected = self.list.inner().clerk().entries.get(index).cloned().flatten();
275298
cx.update(self.as_node(&()));

src/flight_booker.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use chrono::{Duration, Local, NaiveDate, ParseError};
99
use kas::prelude::*;
1010
use kas::widgets::dialog::MessageBox;
11-
use kas::widgets::{column, Adapt, Button, ComboBox, EditBox, EditField, EditGuard, Text};
11+
use kas::widgets::{column, edit, Adapt, Button, ComboBox, EditBox, Text};
1212

1313
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1414
enum Flight {
@@ -92,29 +92,31 @@ impl Guard {
9292
Guard { is_return_field }
9393
}
9494
}
95-
impl EditGuard for Guard {
95+
impl edit::EditGuard for Guard {
9696
type Data = Data;
9797

98-
fn edit(edit: &mut EditField<Self>, cx: &mut EventCx, _: &Self::Data) {
98+
fn edit(&mut self, edit: &mut edit::Editor, cx: &mut EventCx, _: &Self::Data) {
9999
let result = NaiveDate::parse_from_str(edit.as_str().trim(), "%Y-%m-%d");
100-
edit.set_error_state(cx, result.is_err());
100+
if result.is_err() {
101+
edit.set_error(cx, Some("not a valid date".into()));
102+
}
101103

102104
cx.push(ActionDate {
103105
result,
104-
is_return_field: edit.guard.is_return_field,
106+
is_return_field: self.is_return_field,
105107
});
106108
}
107109

108-
fn update(edit: &mut EditField<Self>, cx: &mut ConfigCx, data: &Self::Data) {
109-
if !edit.has_edit_focus() && edit.as_str().is_empty() {
110-
if let Ok(date) = match edit.guard.is_return_field {
110+
fn update(&mut self, edit: &mut edit::Editor, cx: &mut ConfigCx, data: &Self::Data) {
111+
if edit.as_str().is_empty() {
112+
if let Ok(date) = match self.is_return_field {
111113
false => data.out,
112114
true => data.ret,
113115
} {
114116
edit.set_string(cx, date.format("%Y-%m-%d").to_string());
115117
}
116118
}
117-
if edit.guard.is_return_field {
119+
if self.is_return_field {
118120
cx.set_disabled(edit.id(), data.flight == Flight::OneWay);
119121
}
120122
}
@@ -139,7 +141,7 @@ pub fn window() -> Window<()> {
139141
),
140142
EditBox::new(Guard::new(false)),
141143
EditBox::new(Guard::new(true)),
142-
Text::new(|_, data: &Data| format!("{}", data.error)),
144+
Text::new_gen(|_, data: &Data| format!("{}", data.error)),
143145
Button::label_msg("&Book", ActionBook)
144146
.map_any()
145147
.on_update(|cx, _, data: &Data| cx.set_disabled(!data.error.is_none())),
@@ -176,7 +178,7 @@ pub fn window() -> Window<()> {
176178
),
177179
}
178180
};
179-
cx.add_window::<()>(MessageBox::new(msg).into_window("Booker result"));
181+
cx.add_window::<()>(MessageBox::new(msg).into_window("Booker result"), false);
180182
});
181183

182184
Window::new(ui, "Flight Booker").escapable()

0 commit comments

Comments
 (0)