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
Terminalor when it decides to change its own layout, it creates a top-level tile usingTermShare::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
drawmethod on those actors. In that method the actor should draw the required contents on thatTile. -
The actor should then save the
Tileto 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 aTile. -
The
drawmethod 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 newTilewhenever 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
Tilein the actor’sinitmethod, 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
impl Tile
Sourcepub fn new() -> Self
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?
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 }Sourcepub fn sy(&self) -> i32
pub fn sy(&self) -> i32
Get number of rows in Tile (size-Y)
Examples found in repository?
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
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 }Sourcepub fn sx(&self) -> i32
pub fn sx(&self) -> i32
Get number of columns in Tile (size-X)
Examples found in repository?
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
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 }Sourcepub fn split_x(self, x: i32) -> (Self, Self)
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?
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
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 }Sourcepub fn split_xx(self, x0: i32, x1: i32) -> (Self, Self, Self)
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?
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
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 }Sourcepub fn split_yy(self, y0: i32, y1: i32) -> (Self, Self, Self)
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?
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
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 }Sourcepub fn full<'a>(&'a mut self, core: &'a mut Core) -> Option<Region<'a>>
pub fn full<'a>(&'a mut self, core: &'a mut Core) -> Option<Region<'a>>
Examples found in repository?
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
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 }Sourcepub fn region<'a>(
&'a mut self,
core: &'a mut Core,
y: i32,
x: i32,
sy: i32,
sx: i32,
) -> Option<Region<'a>>
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?
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 }