Skip to main content

kanban/
kanban.rs

1// Copyright (c) 2026 John Law
2// SPDX-License-Identifier: MIT
3
4//! A kanban board: drag a card to another place or lane, and click a card to change it, or
5//! to clone, pin, archive, or delete it.
6//!
7//! Run it with `cargo run -p glasskit --example kanban`. It prints what the user does.
8
9use glasskit::Ui;
10use glasskit::events::{Event, EventLoop};
11use glasskit::kanban::{Board, BoardEvent, CardData, CardKey};
12use glasskit::widget::{Button, Column, Insets, Row, Spacer, Text, TextStyle};
13use glasskit::window::{Window, WindowEvent};
14
15const CARDS: [(usize, &str, &str); 5] = [
16    (0, "Write the plan", "One page, with the dates."),
17    (0, "Book the room", ""),
18    (1, "Draft the slides", "Ten slides for the first review."),
19    (2, "Pick a date", ""),
20    (2, "Send the invitations", "To all the people of the team."),
21];
22
23fn main() -> glasskit::Result<()> {
24    let ui = Ui::new()?;
25    let mut events: EventLoop<()> = EventLoop::new(&ui)?;
26    let mut window = Window::new(&ui, "Kanban", 820.0, 520.0)?;
27    window.set_min_size(560.0, 360.0);
28
29    let mut board = Board::new(&mut window, &["To do", "Doing", "Done"]);
30    for (lane, title, body) in CARDS {
31        board.add_card(&mut window, lane, CardData::new(title, body));
32    }
33    let pinned = CardData {
34        pinned: true,
35        ..CardData::new("Ask for the budget", "")
36    };
37    board.add_card(&mut window, 1, pinned);
38
39    let title = window.add(Text::new("Board").style(TextStyle::Title));
40    let gap = window.add(Spacer::fill());
41    let archived = window.add(Button::new("Show archived"));
42    let top = window.add(Row::new().child(title).child(gap).child(archived));
43    let root = window.add(
44        Column::new()
45            .padding(Insets::all(16.0))
46            .gap(12.0)
47            .child(top)
48            .child(board.widget()),
49    );
50    window.set_root(root);
51    window.show();
52
53    loop {
54        match events.next(None)? {
55            Event::Window(id) if id == window.id() => {
56                while let Some(event) = window.next_event() {
57                    match event {
58                        WindowEvent::CloseRequested => return Ok(()),
59                        WindowEvent::Clicked(id) if id == archived => {
60                            let show = !board.show_archived();
61                            board.set_show_archived(&mut window, show);
62                            let label = if show {
63                                "Hide archived"
64                            } else {
65                                "Show archived"
66                            };
67                            window.set_text(archived, label);
68                        }
69                        event => {
70                            if let Some(change) = board.handle(&ui, &mut window, &event)? {
71                                println!("{}", describe(&board, &change));
72                            }
73                        }
74                    }
75                }
76            }
77            Event::Quit => return Ok(()),
78            _ => {}
79        }
80    }
81}
82
83fn describe(board: &Board, change: &BoardEvent) -> String {
84    let title = |key: &CardKey| board.card(*key).map_or("", |card| card.title.as_str());
85    let lane = |key: &CardKey| {
86        let lane = board.place(*key).map_or(0, |(lane, _)| lane);
87        board.lane_name(lane).unwrap_or("")
88    };
89    match change {
90        BoardEvent::Added(key) => format!("added {} to {}", title(key), lane(key)),
91        BoardEvent::Edited(key) => format!("edited {}", title(key)),
92        BoardEvent::Moved(key) => {
93            let at = board.place(*key).map_or(0, |(_, at)| at);
94            format!("moved {} to {} at {at}", title(key), lane(key))
95        }
96        BoardEvent::Cloned { from, .. } => format!("cloned {}", title(from)),
97        BoardEvent::Pinned(key) => format!("pinned {}", title(key)),
98        BoardEvent::Archived(key) => format!("archived {}", title(key)),
99        BoardEvent::Deleted { card, .. } => format!("deleted {}", card.title),
100        _ => String::new(),
101    }
102}