Skip to main content

Tile

Struct Tile 

Source
pub struct Tile { /* private fields */ }
Expand description

A tile of the page

A Tile represents exclusive access to a rectangular region of the page. It may be passed to another actor, and when that actor writes to the tile, the display will be updated automatically using a lazy! handler. This automatically batches changes made by multiple actors into a single terminal update.

Tiles may be split horizontally or vertically. The expected pattern is as follows:

  • When the app receives a resize from the Terminal or when it decides to change its own layout, it creates a top-level tile using TermShare::tile.

  • It breaks this tile into sub-tiles according to the layout of the application

  • The sub-tiles may be passed to other actors responsible for those regions of the screen, for example via a draw method on those actors. In that method the actor should draw the required contents on that Tile.

  • The actor should then save the Tile to use for any updates to the display from then on, i.e. the actor can update that region of the terminal screen with new information whenever it wants to once it has been given a Tile.

  • The draw method of an actor may also break the tile into sub-tiles and pass them on to other actors or other code, as necessary.

Whenever there is another resize or the app decides to change its layout, the same process repeats. Each time a new top-level tile is created using TermShare::tile all previous tiles or sub-tiles are invalidated, and they no longer can be used to write to the terminal. So new sub-tiles have to be created and passed to the relevant actors for them to continue being able to write to the terminal. All active tiles are also invalidated when the terminal is paused (with Terminal::pause) to run an external tool.

There are various models for how to handle actors related to the page, for example:

  • If the actor is long-lived, then have a method fn draw(&mut self, cx: CX![], tile: Tile), and pass it a new Tile whenever the layout changes. The actor can redraw the tile from its state, and optionally pass down sub-tiles to other actors.

  • If the actor doesn’t need to hold long-term state, then pass the Tile in the actor’s init method, and drop the actor and create a new one whenever the layout changes

Since old tiles are invalidated, this is tolerant of just about any approach, since old actors with old tiles but not yet fully shutdown won’t be able to corrupt the terminal display.

Implementations§

Source§

impl Tile

Source

pub fn new() -> Self

Create a zero-size invalid tile that always returns None if you try to write on it. This call may be convenient to initialise an actor’s Tile struct member before the first real tile is passed to it. To create a real tile once you have access to a TermShare, use TermShare::tile.

Examples found in repository?
examples/fontedit/app.rs (line 65)
49    pub fn init(cx: CX![], font: Font) -> Option<Self> {
50        let term = actor!(
51            cx,
52            Terminal::init(
53                SimpleSizer::new(),
54                fwd_to!([cx], resize() as (Option<TermShare>)),
55                fwd_to!([cx], input() as (Key))
56            ),
57            ret_some_to!([cx], |_, cx, cause: StopCause| {
58                println!("Terminal actor failed: {cause}");
59                stop!(cx);
60            })
61        );
62        Some(Self {
63            term,
64            tsh: None,
65            param_tile: Tile::new(),
66            cells_tile: Tile::new(),
67            mini_tile: Tile::new(),
68            font,
69            cx: 0,
70            cy: 0,
71            index: 0,
72            unsaved: false,
73            save_error: None,
74            mode: Mode::Move,
75            clip: Vec::new(),
76            undo: VecDeque::new(),
77        })
78    }
Source

pub fn size(&self) -> (i32, i32)

Get size of Tile: (rows, columns)

Source

pub fn sy(&self) -> i32

Get number of rows in Tile (size-Y)

Examples found in repository?
examples/teeclub/main.rs (line 152)
133    fn layout(&mut self, cx: CX![]) {
134        let Some(ref tsh) = self.tsh else {
135            return;
136        };
137
138        let mut tile = tsh.tile(cx);
139        if let Some(mut r) = tile.full(cx) {
140            r.clear_all_99();
141        }
142        let left_gap = 0.max(tile.sx() - 80) / 2;
143        let right_off = (left_gap + 80).min(tile.sx());
144        let right_gap = tile.sx() - right_off;
145        self.adjust = self.adjust.max(-left_gap).min(right_gap);
146        let left_gap = left_gap + self.adjust;
147        let right_off = (left_gap + 80).min(tile.sx());
148
149        let (_, mid, _) = tile.split_xx(left_gap, right_off);
150        let (main, scores) = mid.split_x(74);
151        self.scores_tile = scores;
152        let sy = main.sy();
153        if let Some(game) = &mut self.game {
154            game.layout(cx, main);
155        } else {
156            let logo_sy = 9;
157            let instr_sy = 16;
158            let gap = (sy - logo_sy - instr_sy) / 3;
159            let (_, mut logo, rest) = main.split_yy(gap, gap + logo_sy);
160            let (_, mut text, _) = rest.split_yy(gap, gap + instr_sy);
161            if let Some(r) = logo.full(cx) {
162                self.draw_logo(r);
163            }
164            if let Some(r) = text.full(cx) {
165                Self::draw_instructions(r);
166            }
167        }
168
169        self.draw_scores(cx);
170    }
More examples
Hide additional examples
examples/fontedit/app.rs (line 95)
85    fn redraw(&mut self, cx: CX![]) {
86        if let Some(ref tsh) = self.tsh {
87            let out = tsh.output(cx);
88            out.attr_99().cursor_show().scroll_up().save_cleanup();
89            out.cursor_hide().clear_all_99_and_remote();
90
91            // Draw fixed stuff here, then break out and store
92            // sub-tiles for areas that need to be updated later
93            let tile = tsh.tile(cx);
94            let sx = tile.sx();
95            let sy = tile.sy();
96            let split_y1 = sy - self.font.sy as i32;
97            let split_y0 = split_y1.min(10);
98            let (top, _, cells_tile) = tile.split_yy(split_y0, split_y1);
99            let spare = sx.saturating_sub(80);
100            let (ox, mini_sx) = if (spare as usize) < self.font.sx / 2 {
101                (spare / 2, 0)
102            } else {
103                (0, spare)
104            };
105            let (_, rest) = top.split_x(ox);
106            let (param_tile, mini_tile, mut keys_tile) = rest.split_xx(40, 40 + mini_sx);
107
108            if let Some(mut r) = keys_tile.region(cx, 0, 0, split_y0, 40) {
109                r.hfb(172).clear_all();
110                let text = "\
111Keys: Arrows move, [Space] change pixel
112Mode on/off: [d] draw, [r] roll
113Fill: [f] flood, [v] vert, [h] horiz
114[n] new glyph, [u] undo, [l] ruler line
115Clip: [c] copy, [P] paste.
116Index: [PgUp] [PgDn] [Home] [End].
117[S] save, [Q] save+quit, [^C] hard quit
118Edit the font file directly to change
119font size or codepoint numbers.";
120                r.text(&text.replace('[', "\0171 ").replace(']', " \0172"));
121            }
122            self.cells_tile = cells_tile;
123            self.param_tile = param_tile;
124            self.mini_tile = mini_tile;
125
126            self.draw_cells(cx);
127            self.draw_mini(cx);
128            self.draw_param(cx);
129        }
130    }
Source

pub fn sx(&self) -> i32

Get number of columns in Tile (size-X)

Examples found in repository?
examples/teeclub/main.rs (line 142)
133    fn layout(&mut self, cx: CX![]) {
134        let Some(ref tsh) = self.tsh else {
135            return;
136        };
137
138        let mut tile = tsh.tile(cx);
139        if let Some(mut r) = tile.full(cx) {
140            r.clear_all_99();
141        }
142        let left_gap = 0.max(tile.sx() - 80) / 2;
143        let right_off = (left_gap + 80).min(tile.sx());
144        let right_gap = tile.sx() - right_off;
145        self.adjust = self.adjust.max(-left_gap).min(right_gap);
146        let left_gap = left_gap + self.adjust;
147        let right_off = (left_gap + 80).min(tile.sx());
148
149        let (_, mid, _) = tile.split_xx(left_gap, right_off);
150        let (main, scores) = mid.split_x(74);
151        self.scores_tile = scores;
152        let sy = main.sy();
153        if let Some(game) = &mut self.game {
154            game.layout(cx, main);
155        } else {
156            let logo_sy = 9;
157            let instr_sy = 16;
158            let gap = (sy - logo_sy - instr_sy) / 3;
159            let (_, mut logo, rest) = main.split_yy(gap, gap + logo_sy);
160            let (_, mut text, _) = rest.split_yy(gap, gap + instr_sy);
161            if let Some(r) = logo.full(cx) {
162                self.draw_logo(r);
163            }
164            if let Some(r) = text.full(cx) {
165                Self::draw_instructions(r);
166            }
167        }
168
169        self.draw_scores(cx);
170    }
More examples
Hide additional examples
examples/fontedit/app.rs (line 94)
85    fn redraw(&mut self, cx: CX![]) {
86        if let Some(ref tsh) = self.tsh {
87            let out = tsh.output(cx);
88            out.attr_99().cursor_show().scroll_up().save_cleanup();
89            out.cursor_hide().clear_all_99_and_remote();
90
91            // Draw fixed stuff here, then break out and store
92            // sub-tiles for areas that need to be updated later
93            let tile = tsh.tile(cx);
94            let sx = tile.sx();
95            let sy = tile.sy();
96            let split_y1 = sy - self.font.sy as i32;
97            let split_y0 = split_y1.min(10);
98            let (top, _, cells_tile) = tile.split_yy(split_y0, split_y1);
99            let spare = sx.saturating_sub(80);
100            let (ox, mini_sx) = if (spare as usize) < self.font.sx / 2 {
101                (spare / 2, 0)
102            } else {
103                (0, spare)
104            };
105            let (_, rest) = top.split_x(ox);
106            let (param_tile, mini_tile, mut keys_tile) = rest.split_xx(40, 40 + mini_sx);
107
108            if let Some(mut r) = keys_tile.region(cx, 0, 0, split_y0, 40) {
109                r.hfb(172).clear_all();
110                let text = "\
111Keys: Arrows move, [Space] change pixel
112Mode on/off: [d] draw, [r] roll
113Fill: [f] flood, [v] vert, [h] horiz
114[n] new glyph, [u] undo, [l] ruler line
115Clip: [c] copy, [P] paste.
116Index: [PgUp] [PgDn] [Home] [End].
117[S] save, [Q] save+quit, [^C] hard quit
118Edit the font file directly to change
119font size or codepoint numbers.";
120                r.text(&text.replace('[', "\0171 ").replace(']', " \0172"));
121            }
122            self.cells_tile = cells_tile;
123            self.param_tile = param_tile;
124            self.mini_tile = mini_tile;
125
126            self.draw_cells(cx);
127            self.draw_mini(cx);
128            self.draw_param(cx);
129        }
130    }
Source

pub fn split_x(self, x: i32) -> (Self, Self)

Split a Tile in two at a particular X-position (relative to the tile’s left edge). This consumes the original Tile. The X-value is cropped to the size of the tile.

Examples found in repository?
examples/teeclub/main.rs (line 150)
133    fn layout(&mut self, cx: CX![]) {
134        let Some(ref tsh) = self.tsh else {
135            return;
136        };
137
138        let mut tile = tsh.tile(cx);
139        if let Some(mut r) = tile.full(cx) {
140            r.clear_all_99();
141        }
142        let left_gap = 0.max(tile.sx() - 80) / 2;
143        let right_off = (left_gap + 80).min(tile.sx());
144        let right_gap = tile.sx() - right_off;
145        self.adjust = self.adjust.max(-left_gap).min(right_gap);
146        let left_gap = left_gap + self.adjust;
147        let right_off = (left_gap + 80).min(tile.sx());
148
149        let (_, mid, _) = tile.split_xx(left_gap, right_off);
150        let (main, scores) = mid.split_x(74);
151        self.scores_tile = scores;
152        let sy = main.sy();
153        if let Some(game) = &mut self.game {
154            game.layout(cx, main);
155        } else {
156            let logo_sy = 9;
157            let instr_sy = 16;
158            let gap = (sy - logo_sy - instr_sy) / 3;
159            let (_, mut logo, rest) = main.split_yy(gap, gap + logo_sy);
160            let (_, mut text, _) = rest.split_yy(gap, gap + instr_sy);
161            if let Some(r) = logo.full(cx) {
162                self.draw_logo(r);
163            }
164            if let Some(r) = text.full(cx) {
165                Self::draw_instructions(r);
166            }
167        }
168
169        self.draw_scores(cx);
170    }
More examples
Hide additional examples
examples/fontedit/app.rs (line 105)
85    fn redraw(&mut self, cx: CX![]) {
86        if let Some(ref tsh) = self.tsh {
87            let out = tsh.output(cx);
88            out.attr_99().cursor_show().scroll_up().save_cleanup();
89            out.cursor_hide().clear_all_99_and_remote();
90
91            // Draw fixed stuff here, then break out and store
92            // sub-tiles for areas that need to be updated later
93            let tile = tsh.tile(cx);
94            let sx = tile.sx();
95            let sy = tile.sy();
96            let split_y1 = sy - self.font.sy as i32;
97            let split_y0 = split_y1.min(10);
98            let (top, _, cells_tile) = tile.split_yy(split_y0, split_y1);
99            let spare = sx.saturating_sub(80);
100            let (ox, mini_sx) = if (spare as usize) < self.font.sx / 2 {
101                (spare / 2, 0)
102            } else {
103                (0, spare)
104            };
105            let (_, rest) = top.split_x(ox);
106            let (param_tile, mini_tile, mut keys_tile) = rest.split_xx(40, 40 + mini_sx);
107
108            if let Some(mut r) = keys_tile.region(cx, 0, 0, split_y0, 40) {
109                r.hfb(172).clear_all();
110                let text = "\
111Keys: Arrows move, [Space] change pixel
112Mode on/off: [d] draw, [r] roll
113Fill: [f] flood, [v] vert, [h] horiz
114[n] new glyph, [u] undo, [l] ruler line
115Clip: [c] copy, [P] paste.
116Index: [PgUp] [PgDn] [Home] [End].
117[S] save, [Q] save+quit, [^C] hard quit
118Edit the font file directly to change
119font size or codepoint numbers.";
120                r.text(&text.replace('[', "\0171 ").replace(']', " \0172"));
121            }
122            self.cells_tile = cells_tile;
123            self.param_tile = param_tile;
124            self.mini_tile = mini_tile;
125
126            self.draw_cells(cx);
127            self.draw_mini(cx);
128            self.draw_param(cx);
129        }
130    }
Source

pub fn split_xx(self, x0: i32, x1: i32) -> (Self, Self, Self)

Split a Tile in 3 parts, at the given X-positions (relative to the tile’s left edge). The X-values are cropped to the size of the tile.

Examples found in repository?
examples/teeclub/main.rs (line 149)
133    fn layout(&mut self, cx: CX![]) {
134        let Some(ref tsh) = self.tsh else {
135            return;
136        };
137
138        let mut tile = tsh.tile(cx);
139        if let Some(mut r) = tile.full(cx) {
140            r.clear_all_99();
141        }
142        let left_gap = 0.max(tile.sx() - 80) / 2;
143        let right_off = (left_gap + 80).min(tile.sx());
144        let right_gap = tile.sx() - right_off;
145        self.adjust = self.adjust.max(-left_gap).min(right_gap);
146        let left_gap = left_gap + self.adjust;
147        let right_off = (left_gap + 80).min(tile.sx());
148
149        let (_, mid, _) = tile.split_xx(left_gap, right_off);
150        let (main, scores) = mid.split_x(74);
151        self.scores_tile = scores;
152        let sy = main.sy();
153        if let Some(game) = &mut self.game {
154            game.layout(cx, main);
155        } else {
156            let logo_sy = 9;
157            let instr_sy = 16;
158            let gap = (sy - logo_sy - instr_sy) / 3;
159            let (_, mut logo, rest) = main.split_yy(gap, gap + logo_sy);
160            let (_, mut text, _) = rest.split_yy(gap, gap + instr_sy);
161            if let Some(r) = logo.full(cx) {
162                self.draw_logo(r);
163            }
164            if let Some(r) = text.full(cx) {
165                Self::draw_instructions(r);
166            }
167        }
168
169        self.draw_scores(cx);
170    }
More examples
Hide additional examples
examples/fontedit/app.rs (line 106)
85    fn redraw(&mut self, cx: CX![]) {
86        if let Some(ref tsh) = self.tsh {
87            let out = tsh.output(cx);
88            out.attr_99().cursor_show().scroll_up().save_cleanup();
89            out.cursor_hide().clear_all_99_and_remote();
90
91            // Draw fixed stuff here, then break out and store
92            // sub-tiles for areas that need to be updated later
93            let tile = tsh.tile(cx);
94            let sx = tile.sx();
95            let sy = tile.sy();
96            let split_y1 = sy - self.font.sy as i32;
97            let split_y0 = split_y1.min(10);
98            let (top, _, cells_tile) = tile.split_yy(split_y0, split_y1);
99            let spare = sx.saturating_sub(80);
100            let (ox, mini_sx) = if (spare as usize) < self.font.sx / 2 {
101                (spare / 2, 0)
102            } else {
103                (0, spare)
104            };
105            let (_, rest) = top.split_x(ox);
106            let (param_tile, mini_tile, mut keys_tile) = rest.split_xx(40, 40 + mini_sx);
107
108            if let Some(mut r) = keys_tile.region(cx, 0, 0, split_y0, 40) {
109                r.hfb(172).clear_all();
110                let text = "\
111Keys: Arrows move, [Space] change pixel
112Mode on/off: [d] draw, [r] roll
113Fill: [f] flood, [v] vert, [h] horiz
114[n] new glyph, [u] undo, [l] ruler line
115Clip: [c] copy, [P] paste.
116Index: [PgUp] [PgDn] [Home] [End].
117[S] save, [Q] save+quit, [^C] hard quit
118Edit the font file directly to change
119font size or codepoint numbers.";
120                r.text(&text.replace('[', "\0171 ").replace(']', " \0172"));
121            }
122            self.cells_tile = cells_tile;
123            self.param_tile = param_tile;
124            self.mini_tile = mini_tile;
125
126            self.draw_cells(cx);
127            self.draw_mini(cx);
128            self.draw_param(cx);
129        }
130    }
Source

pub fn split_y(self, y: i32) -> (Self, Self)

Split a Tile in two at a particular Y-position (relative to the tile’s top edge). This consumes the original Tile. The Y-value is cropped to the size of the tile.

Source

pub fn split_yy(self, y0: i32, y1: i32) -> (Self, Self, Self)

Split a Tile in 3 parts, at the given Y-positions (relative to the tile’s top edge). The Y-values are cropped to the size of the tile.

Examples found in repository?
examples/teeclub/main.rs (line 159)
133    fn layout(&mut self, cx: CX![]) {
134        let Some(ref tsh) = self.tsh else {
135            return;
136        };
137
138        let mut tile = tsh.tile(cx);
139        if let Some(mut r) = tile.full(cx) {
140            r.clear_all_99();
141        }
142        let left_gap = 0.max(tile.sx() - 80) / 2;
143        let right_off = (left_gap + 80).min(tile.sx());
144        let right_gap = tile.sx() - right_off;
145        self.adjust = self.adjust.max(-left_gap).min(right_gap);
146        let left_gap = left_gap + self.adjust;
147        let right_off = (left_gap + 80).min(tile.sx());
148
149        let (_, mid, _) = tile.split_xx(left_gap, right_off);
150        let (main, scores) = mid.split_x(74);
151        self.scores_tile = scores;
152        let sy = main.sy();
153        if let Some(game) = &mut self.game {
154            game.layout(cx, main);
155        } else {
156            let logo_sy = 9;
157            let instr_sy = 16;
158            let gap = (sy - logo_sy - instr_sy) / 3;
159            let (_, mut logo, rest) = main.split_yy(gap, gap + logo_sy);
160            let (_, mut text, _) = rest.split_yy(gap, gap + instr_sy);
161            if let Some(r) = logo.full(cx) {
162                self.draw_logo(r);
163            }
164            if let Some(r) = text.full(cx) {
165                Self::draw_instructions(r);
166            }
167        }
168
169        self.draw_scores(cx);
170    }
More examples
Hide additional examples
examples/fontedit/app.rs (line 98)
85    fn redraw(&mut self, cx: CX![]) {
86        if let Some(ref tsh) = self.tsh {
87            let out = tsh.output(cx);
88            out.attr_99().cursor_show().scroll_up().save_cleanup();
89            out.cursor_hide().clear_all_99_and_remote();
90
91            // Draw fixed stuff here, then break out and store
92            // sub-tiles for areas that need to be updated later
93            let tile = tsh.tile(cx);
94            let sx = tile.sx();
95            let sy = tile.sy();
96            let split_y1 = sy - self.font.sy as i32;
97            let split_y0 = split_y1.min(10);
98            let (top, _, cells_tile) = tile.split_yy(split_y0, split_y1);
99            let spare = sx.saturating_sub(80);
100            let (ox, mini_sx) = if (spare as usize) < self.font.sx / 2 {
101                (spare / 2, 0)
102            } else {
103                (0, spare)
104            };
105            let (_, rest) = top.split_x(ox);
106            let (param_tile, mini_tile, mut keys_tile) = rest.split_xx(40, 40 + mini_sx);
107
108            if let Some(mut r) = keys_tile.region(cx, 0, 0, split_y0, 40) {
109                r.hfb(172).clear_all();
110                let text = "\
111Keys: Arrows move, [Space] change pixel
112Mode on/off: [d] draw, [r] roll
113Fill: [f] flood, [v] vert, [h] horiz
114[n] new glyph, [u] undo, [l] ruler line
115Clip: [c] copy, [P] paste.
116Index: [PgUp] [PgDn] [Home] [End].
117[S] save, [Q] save+quit, [^C] hard quit
118Edit the font file directly to change
119font size or codepoint numbers.";
120                r.text(&text.replace('[', "\0171 ").replace(']', " \0172"));
121            }
122            self.cells_tile = cells_tile;
123            self.param_tile = param_tile;
124            self.mini_tile = mini_tile;
125
126            self.draw_cells(cx);
127            self.draw_mini(cx);
128            self.draw_param(cx);
129        }
130    }
Source

pub fn full<'a>(&'a mut self, core: &'a mut Core) -> Option<Region<'a>>

Get a Region that allows writing to the whole Tile, or return None if this tile is now invalid.

Examples found in repository?
examples/teeclub/main.rs (line 127)
122    fn redraw(&mut self, cx: CX![]) {
123        let Some(ref tsh) = self.tsh else {
124            return;
125        };
126        let mut tile = tsh.tile(cx);
127        if let Some(mut r) = tile.full(cx) {
128            r.clear_all_99();
129        }
130        after!(Duration::from_millis(10), [cx], layout());
131    }
132
133    fn layout(&mut self, cx: CX![]) {
134        let Some(ref tsh) = self.tsh else {
135            return;
136        };
137
138        let mut tile = tsh.tile(cx);
139        if let Some(mut r) = tile.full(cx) {
140            r.clear_all_99();
141        }
142        let left_gap = 0.max(tile.sx() - 80) / 2;
143        let right_off = (left_gap + 80).min(tile.sx());
144        let right_gap = tile.sx() - right_off;
145        self.adjust = self.adjust.max(-left_gap).min(right_gap);
146        let left_gap = left_gap + self.adjust;
147        let right_off = (left_gap + 80).min(tile.sx());
148
149        let (_, mid, _) = tile.split_xx(left_gap, right_off);
150        let (main, scores) = mid.split_x(74);
151        self.scores_tile = scores;
152        let sy = main.sy();
153        if let Some(game) = &mut self.game {
154            game.layout(cx, main);
155        } else {
156            let logo_sy = 9;
157            let instr_sy = 16;
158            let gap = (sy - logo_sy - instr_sy) / 3;
159            let (_, mut logo, rest) = main.split_yy(gap, gap + logo_sy);
160            let (_, mut text, _) = rest.split_yy(gap, gap + instr_sy);
161            if let Some(r) = logo.full(cx) {
162                self.draw_logo(r);
163            }
164            if let Some(r) = text.full(cx) {
165                Self::draw_instructions(r);
166            }
167        }
168
169        self.draw_scores(cx);
170    }
171
172    fn draw_scores(&mut self, cx: CX![]) {
173        let mut score = self.score;
174        let mut last_score = self.last_score;
175        if let Some(mut r) = self.scores_tile.full(cx) {
176            r.hfb(176).clear_all();
177            r.hfb(7).text("SCORES");
178            let mut y = 1;
179            let last_y = r.sy() - 1;
180            let mut draw = move |hfb, sc| {
181                if y > 0 {
182                    r.at(y, 0).hfb(hfb);
183                    write!(r, "{:3}:{:02}", sc / 60, sc % 60).unwrap();
184                    y += 1;
185                }
186            };
187
188            for &s0 in &self.scores {
189                if let Some(curr) = score
190                    && (curr < s0 || y == last_y)
191                {
192                    draw(7, curr);
193                    score = None;
194                }
195                if Some(s0) == last_score {
196                    draw(173, s0);
197                    last_score = None;
198                } else {
199                    draw(176, s0);
200                }
201            }
202            if let Some(s1) = score {
203                draw(173, s1);
204            }
205        }
206    }
207
208    fn draw_logo(&self, mut r: Region<'_>) {
209        const LOGO_HFB: [u16; 30] = [
210            160, 160, 160, 140, 140, 140, 140, 140, 150, 150, 150, 150, 150, 110, 110, 110, 110,
211            110, 130, 130, 130, 130, 130, 120, 120, 120, 120, 120, 160, 160,
212        ];
213        const LOGO: [&str; 9] = [
214            r"  ___________________________________      ",
215            r" / ____  ____________________________\     ",
216            r"/ /   / /                __      __  __    ",
217            r"\ \  / / ___  ___  _____/ /_  __/ /_ \ \   ",
218            r" \/ / / / _ \/ _ \/ ___/ / / / / __ \ \ \  ",
219            r"   / / /  __/  __/ /__/ / /_/ / /_/ /  \ \ ",
220            r"   \_\ \___/\___/\___/_/\__,_/_.___/ /_ \ \",
221            r"     ___________________________________/ /",
222            r"     \___________________________________/ ",
223        ];
224        let logo_hfb = match self.logo_hfb.as_ref() {
225            Some(v) => v.as_slice(),
226            None => LOGO_HFB.as_slice(),
227        };
228        let ox = (r.sx() - 43) / 2;
229        let oy = (r.sy() - 9) / 2;
230        for (y, s) in LOGO.iter().enumerate() {
231            r.at(oy + y as i32, ox);
232            for (x, c) in s.chars().enumerate() {
233                r.hfb(logo_hfb[(10 + x + y) % 30]);
234                r.char(c);
235            }
236        }
237    }
238
239    fn draw_instructions(mut r: Region<'_>) {
240        const TEXT: &str = r"
241
242        Keys: [N] New game; [Q] Quit; [1] to [9] Select cards in
243        corresponding column; [+]/[=] or [-] Increase or decrease
244        number of selected cards; [1] to [9] If the same digit as the
245        selecting keypress, move selected cards to top, if another
246        digit, move as many of selected cards as possible to that
247        column; [Space] Bring a new card down from the stock pile;
248        [BackSp] Go back one move; [<]/[>] Adjust display left/right.
249
250        Rules: The aim is to move all the cards to the top area, the
251        spaces the the right of the stock pile.  Each top pile must
252        consist of one suit only, stacked in order from Ace up to
253        King.  The cards in the main area can be moved around:
254        sequences of one or more cards of the same suit may be moved
255        on top of a card with the next-higher number, of any suit.
256        The bulk of the cards are in the stock pile in the top-left.
257        Cards may be brought down from there using Space.
258
259        ";
260        let lines: Vec<_> = TEXT.trim().split('\n').map(|s| s.to_string()).collect();
261        let n_lines = lines.len();
262        let oy = (r.sy() - n_lines as i32) / 2;
263        for (i, line) in lines.iter().enumerate() {
264            r.at(i as i32 + oy, 7);
265            let line = line.trim().replace("[", "\0171 ").replace("]", " \0099");
266            r.text(&line);
267        }
268    }
269
270    fn input(&mut self, cx: CX![], key: Key) {
271        match key {
272            Key::Ctrl('L') => self.redraw(cx),
273            Key::Ctrl('C') => stop!(cx),
274            Key::Pr('q' | 'Q') => {
275                if self.game.is_some() {
276                    self.game = None;
277                    self.score = None;
278                    self.layout(cx);
279                } else {
280                    stop!(cx);
281                }
282            }
283            Key::Pr('n' | 'N') => {
284                self.game = Some(Game::new(
285                    cx.now(),
286                    ret_some_to!([cx], game_finished() as (Duration)),
287                ));
288                self.last_score = None;
289                self.update_score(cx);
290                self.layout(cx);
291            }
292            Key::Pr('<') => {
293                self.adjust -= 1;
294                self.save_scores().expect("Failed to save scores");
295                self.layout(cx);
296            }
297            Key::Pr('>') => {
298                self.adjust += 1;
299                self.save_scores().expect("Failed to save scores");
300                self.layout(cx);
301            }
302            _ => {
303                if let Some(game) = &mut self.game {
304                    game.input(cx, key);
305                }
306            }
307        }
308    }
309
310    fn update_score(&mut self, cx: CX![]) {
311        if let Some(game) = &mut self.game {
312            let dur = game.score(cx);
313            let score = dur.as_secs() as i32;
314            let to_wait = if Some(score) != self.score {
315                self.score = Some(score);
316                self.draw_scores(cx);
317
318                // Update 100ms after next change
319                1_000_000_000 - dur.subsec_nanos() + 100_000_000
320            } else {
321                1_000_000_000
322            };
323            after!(Duration::from_nanos(to_wait as u64), [cx], update_score());
324        }
325    }
326
327    fn game_finished(&mut self, cx: CX![], dur: Duration) {
328        let score = dur.as_secs() as i32;
329        self.scores.push(score);
330        self.scores.sort_unstable();
331        self.save_scores().expect("Failed to save scores");
332        self.last_score = Some(score);
333        self.score = None;
334        self.game = None;
335        self.layout(cx);
336    }
337
338    fn score_path() -> PathBuf {
339        let mut path = std::env::home_dir().unwrap_or_default();
340        path.push(".teeclub-scores");
341        path
342    }
343
344    fn load_scores(&mut self) -> Option<()> {
345        let data = std::fs::read_to_string(Self::score_path()).ok()?;
346        let mut it = data.split(char::is_whitespace);
347        while let Some(tok) = it.next() {
348            match tok {
349                "adjust:" => {
350                    self.adjust = it.next()?.parse::<i32>().ok()?;
351                }
352                "scores[" => {
353                    self.scores = Vec::new();
354                    loop {
355                        match it.next()? {
356                            "]" => break,
357                            v => self.scores.push(v.parse::<i32>().ok()?),
358                        }
359                    }
360                    self.scores.sort_unstable();
361                }
362                "" => (),
363                _ => return None,
364            }
365        }
366        Some(())
367    }
368
369    fn save_scores(&mut self) -> std::io::Result<()> {
370        let mut out = Vec::new();
371        let _ = writeln!(out, "adjust: {}", self.adjust);
372        let _ = writeln!(out, "scores[");
373        for sc in self.scores.iter().take(200) {
374            let _ = writeln!(out, "{sc}");
375        }
376        let _ = writeln!(out, "]");
377        std::fs::write(Self::score_path(), out)
378    }
379}
380
381/// Game state
382#[derive(Eq, PartialEq)]
383struct State {
384    pile: [Hand; 9],
385    stack: [Hand; 9],
386}
387
388impl State {
389    fn pack(&self) -> Vec<u8> {
390        let mut out = Vec::new();
391        for p in &self.pile {
392            p.pack(&mut out);
393        }
394        for s in &self.stack {
395            s.pack(&mut out);
396        }
397        out
398    }
399
400    fn unpack(mut data: &[u8]) -> Self {
401        let mut pile: [Hand; 9] = Default::default();
402        let mut stack: [Hand; 9] = Default::default();
403        for p in &mut pile {
404            *p = Hand::unpack(&mut data);
405        }
406        for s in &mut stack {
407            *s = Hand::unpack(&mut data);
408        }
409        Self { pile, stack }
410    }
411}
412
413/// Game state and gameplay handling
414struct Game {
415    tile: Tile,
416    history: Vec<Vec<u8>>,
417    state: State,
418    select: Option<(usize, usize)>, // (index, count)
419    dur: Duration,
420    activity: Instant,
421    ret: Option<Ret<Duration>>, // Report score for game completion
422}
423
424impl Game {
425    fn new(start: Instant, ret: Ret<Duration>) -> Self {
426        let seed = std::time::SystemTime::UNIX_EPOCH
427            .elapsed()
428            .map(|d| d.as_nanos() as u64)
429            .unwrap_or(0);
430
431        let mut rand = Rand32::new(seed);
432        let mut pile: [Hand; 9] = Default::default();
433        let mut stack: [Hand; 9] = Default::default();
434
435        let p0 = &mut pile[0];
436        p0.add_deck();
437        p0.add_deck();
438        p0.shuffle(&mut rand);
439        p0.shuffle(&mut rand);
440
441        for _ in 0..5 {
442            for s in &mut stack {
443                if let Some(card) = p0.pick_last() {
444                    s.add(card);
445                }
446            }
447        }
448
449        Self {
450            tile: Tile::default(),
451            history: Vec::new(),
452            state: State { pile, stack },
453            select: None,
454            dur: Duration::from_secs(0),
455            activity: start,
456            ret: Some(ret),
457        }
458    }
459
460    fn select(&mut self, core: &mut Core, i: usize) {
461        let s = &self.state.stack[i];
462        let len = s.len();
463        if len == 0 {
464            self.select = None;
465        } else {
466            let mut count = 1;
467            while count < len && s[len - count].inc == Some(s[len - count - 1]) {
468                count += 1;
469            }
470            self.select = Some((i, count));
471        }
472        self.draw(core);
473    }
474
475    fn sel_inc(&mut self, core: &mut Core) {
476        if let Some((i, count)) = self.select {
477            let s = &self.state.stack[i];
478            let len = s.len();
479            if count < len && s[len - count].inc == Some(s[len - count - 1]) {
480                self.select = Some((i, count + 1));
481                self.draw(core);
482            }
483        }
484    }
485
486    fn sel_dec(&mut self, core: &mut Core) {
487        if let Some(sel) = &mut self.select {
488            let i = sel.0;
489            let count = sel.1;
490            if count <= 1 {
491                self.select = None;
492            } else {
493                self.select = Some((i, count - 1));
494            }
495            self.draw(core);
496        }
497    }
498
499    /// Mark this state by saving it in history, available to undo
500    /// later
501    fn mark(&mut self) {
502        let v = self.state.pack();
503        if let Some(last) = self.history.last()
504            && *last == v
505        {
506            return;
507        }
508        self.history.push(v);
509    }
510
511    /// Restore most recent marked state and remove it from history
512    fn undo(&mut self, core: &mut Core) {
513        if let Some(data) = self.history.pop() {
514            self.state = State::unpack(&data);
515            self.select = None;
516            self.draw(core);
517        }
518    }
519
520    /// Move selected cards to the given stack or to top if the stack
521    /// is the selected stack
522    fn move_to(&mut self, core: &mut Core, to: usize) {
523        let Some((fr, cnt)) = self.select else {
524            return;
525        };
526        self.select = None;
527        self.mark();
528
529        let s = &mut self.state;
530        let Some(fr_last) = s.stack[fr].peek_last() else {
531            return;
532        };
533
534        if fr == to {
535            // Move to top
536            for pi in (1..9).rev() {
537                let do_move = if let Some(to_last) = s.pile[pi].peek_last() {
538                    to_last.inc == Some(fr_last)
539                } else {
540                    fr_last.num == 1
541                };
542                if do_move {
543                    for _ in 0..cnt {
544                        if let Some(card) = s.stack[fr].pick_last() {
545                            s.pile[pi].add(card);
546                        }
547                    }
548                    break;
549                }
550            }
551            if self.is_finished()
552                && let Some(ret) = self.ret.take()
553            {
554                ret!([ret], self.score(core));
555            }
556        } else {
557            // Move to another pile
558            let mut copy_cnt = cnt;
559            if let Some(to_last) = s.stack[to].peek_last() {
560                copy_cnt = copy_cnt.min(to_last.num.saturating_sub(fr_last.num) as usize);
561                if to_last.num != fr_last.num + copy_cnt as u8 {
562                    copy_cnt = 0;
563                }
564            }
565
566            let mut tmp = Vec::new();
567            for _ in 0..copy_cnt {
568                tmp.push(s.stack[fr].pick_last().unwrap());
569            }
570            while let Some(card) = tmp.pop() {
571                s.stack[to].add(card);
572            }
573        }
574        self.draw(core);
575    }
576
577    /// Move card down from stock pile
578    fn move_down(&mut self, core: &mut Core) {
579        self.mark();
580        if let Some(card) = self.state.pile[0].pick_last() {
581            self.state.stack[0].add(card);
582            self.select = None;
583            self.draw(core);
584        }
585    }
586
587    fn is_finished(&mut self) -> bool {
588        for i in 1..9 {
589            if self.state.pile[i].len() != 13 {
590                return false;
591            }
592        }
593        true
594    }
595
596    /// Set the tile to use for drawing, and redraw
597    fn layout(&mut self, core: &mut Core, tile: Tile) {
598        self.tile = tile;
599        self.draw(core);
600    }
601
602    /// Draw the playing area
603    fn draw(&mut self, core: &mut Core) {
604        if let Some(mut r) = self.tile.full(core) {
605            r.clear_all_99();
606
607            for col in 0..9 {
608                let x = 1 + col as i32 * 8;
609
610                // Pile
611                if let Some(card) = self.state.pile[col].peek_last() {
612                    if col == 0 {
613                        // Facedown pile
614                        draw::card_back(&mut r, 0, x);
615                    } else {
616                        draw::card(&mut r, 0, x, card);
617                    }
618                } else {
619                    draw::card_space(&mut r, 0, x);
620                }
621
622                // Index number
623                r.hfb(7)
624                    .at(CARD_SY, x)
625                    .hfb(70)
626                    .char((49 + col as u8) as char);
627
628                // Stack
629                let y0 = CARD_SY + 1;
630                let y1 = r.sy();
631                let hand = &self.state.stack[col];
632                draw::card_space(&mut r, y0, x);
633
634                let selected = self
635                    .select
636                    .map(|(index, count)| if col == index { count } else { 0 })
637                    .unwrap_or(0);
638                let len = hand.len();
639                let sel_i = len.saturating_sub(selected);
640                let space = y1 - y0 - 1;
641                let mut y = y0;
642                let mut i = 0;
643                if len as i32 + CARD_SY > space {
644                    draw::card_dots(&mut r, y, x);
645                    y += 1;
646                    i = len - (space - CARD_SY) as usize;
647                }
648                while i < hand.len() {
649                    let card = hand[i];
650                    if i >= sel_i {
651                        draw::card(&mut r, y + 1, x + 1, card);
652                    } else {
653                        draw::card(&mut r, y, x, card);
654                    }
655                    i += 1;
656                    y += 1;
657                }
658            }
659        }
660    }
More examples
Hide additional examples
examples/fontedit/app.rs (line 133)
132    fn draw_param(&mut self, cx: CX![]) {
133        if let Some(mut r) = self.param_tile.full(cx) {
134            r.hfb(172).clear_all();
135
136            // Use sub-region to clip the filename if it's too long
137            r.region(0, 0, 1, 39)
138                .hfb(172)
139                .text("File:  ")
140                .text(&self.font.path.to_string_lossy());
141
142            let count = self.font.index_limit();
143            let index = self.index;
144
145            let state = if let Some(ref err) = self.save_error {
146                err
147            } else if self.unsaved {
148                "UNSAVED"
149            } else {
150                ""
151            };
152            let cp = self.font.chars[index].cp;
153            let ch = match cp {
154                32..=126 | 160.. => char::from_u32(cp).unwrap_or('?'),
155                _ => '?',
156            };
157            let sx = self.font.sx;
158            let sy = self.font.sy;
159
160            r.at(1, 0);
161            let _ = writeln!(r, "Size:  {sx} x {sy}, {count} glyphs");
162            let _ = writeln!(r, "Index: {index} U+{cp:04x} '{ch}'");
163            let _ = writeln!(r, "Mode:  {}", self.mode.caps());
164            let _ = writeln!(r, "State: {}", state);
165        }
166    }
167
168    fn draw_cells(&mut self, cx: CX![]) {
169        if let Some(mut full) = self.cells_tile.full(cx) {
170            let sx = full.sx();
171            full.clear_all_99();
172
173            let font_sx = self.font.sx;
174            let font_sy = self.font.sy;
175            let cell_wid = font_sx * 2;
176            let r = full.region(
177                0,
178                (sx - cell_wid as i32) / 2,
179                font_sy as i32,
180                cell_wid as i32,
181            );
182            draw_cell(
183                &self.font,
184                r,
185                self.index,
186                171,
187                7,
188                Some((self.cy, self.cx, self.mode)),
189            );
190            let max = self.font.index_limit();
191            for i in 1..=self.index {
192                let r = full.region(
193                    0,
194                    (sx - (cell_wid * (1 + i * 2)) as i32) / 2,
195                    font_sy as i32,
196                    cell_wid as i32,
197                );
198                if !r.is_visible() {
199                    break;
200                }
201                draw_cell(&self.font, r, self.index - i, 70, 6, None);
202            }
203            for i in 1..(max - self.index) {
204                let r = full.region(
205                    0,
206                    (sx + (cell_wid * (i * 2 - 1)) as i32) / 2,
207                    font_sy as i32,
208                    cell_wid as i32,
209                );
210                if !r.is_visible() {
211                    break;
212                }
213                draw_cell(&self.font, r, self.index + i, 70, 6, None);
214            }
215        }
216    }
217
218    fn draw_mini(&mut self, cx: CX![]) {
219        if let Some(mut full) = self.mini_tile.full(cx) {
220            full.clear_all_99();
221            let sx = full.sx() * 2;
222            let sy = full.sy() * 4;
223            let font_sx = self.font.sx as i32;
224            let font_sy = self.font.sy as i32;
225            let oy = sy.saturating_sub(font_sy) / 2;
226            let mut ox = (sx - font_sx) / 2;
227            let mut index = self.index;
228            while ox > 0 && index > 0 {
229                ox -= font_sx;
230                index -= 1;
231            }
232            while ox < sx {
233                draw_mini_cell(&self.font, full.full(), oy, ox, index, 170);
234                ox += font_sx;
235                index += 1;
236            }
237        }
238    }
Source

pub fn region<'a>( &'a mut self, core: &'a mut Core, y: i32, x: i32, sy: i32, sx: i32, ) -> Option<Region<'a>>

Get a Region that allows writing to just part of the Tile, or return None if this tile is now invalid.

Examples found in repository?
examples/fontedit/app.rs (line 108)
85    fn redraw(&mut self, cx: CX![]) {
86        if let Some(ref tsh) = self.tsh {
87            let out = tsh.output(cx);
88            out.attr_99().cursor_show().scroll_up().save_cleanup();
89            out.cursor_hide().clear_all_99_and_remote();
90
91            // Draw fixed stuff here, then break out and store
92            // sub-tiles for areas that need to be updated later
93            let tile = tsh.tile(cx);
94            let sx = tile.sx();
95            let sy = tile.sy();
96            let split_y1 = sy - self.font.sy as i32;
97            let split_y0 = split_y1.min(10);
98            let (top, _, cells_tile) = tile.split_yy(split_y0, split_y1);
99            let spare = sx.saturating_sub(80);
100            let (ox, mini_sx) = if (spare as usize) < self.font.sx / 2 {
101                (spare / 2, 0)
102            } else {
103                (0, spare)
104            };
105            let (_, rest) = top.split_x(ox);
106            let (param_tile, mini_tile, mut keys_tile) = rest.split_xx(40, 40 + mini_sx);
107
108            if let Some(mut r) = keys_tile.region(cx, 0, 0, split_y0, 40) {
109                r.hfb(172).clear_all();
110                let text = "\
111Keys: Arrows move, [Space] change pixel
112Mode on/off: [d] draw, [r] roll
113Fill: [f] flood, [v] vert, [h] horiz
114[n] new glyph, [u] undo, [l] ruler line
115Clip: [c] copy, [P] paste.
116Index: [PgUp] [PgDn] [Home] [End].
117[S] save, [Q] save+quit, [^C] hard quit
118Edit the font file directly to change
119font size or codepoint numbers.";
120                r.text(&text.replace('[', "\0171 ").replace(']', " \0172"));
121            }
122            self.cells_tile = cells_tile;
123            self.param_tile = param_tile;
124            self.mini_tile = mini_tile;
125
126            self.draw_cells(cx);
127            self.draw_mini(cx);
128            self.draw_param(cx);
129        }
130    }

Trait Implementations§

Source§

impl Debug for Tile

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Tile

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl Freeze for Tile

§

impl !RefUnwindSafe for Tile

§

impl !Send for Tile

§

impl !Sync for Tile

§

impl Unpin for Tile

§

impl UnsafeUnpin for Tile

§

impl !UnwindSafe for Tile

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.