Skip to main content

Region

Struct Region 

Source
pub struct Region<'a> { /* private fields */ }
Expand description

A temporary view of a logical region of a Page that allows writing text

The region is logical in the sense that it may be partly or fully outside of the actual boundaries of the page, or of any region it was derived from. All data written is only written if it is within the clip. The clip is the smallest of the page, the region and all the parent regions. However on writing, the logical write position is advanced as if the characters were written in any case.

This acts like a mini terminal in that it remembers its current hfb colour and write position, and lets you change them, and write text to the ‘terminal’. Text will character-wrap at the region’s logical right boundary back to the region’s logical left boundary. This also implements the Write trait so it can be used for formatted output with write!(region, ...).

However note that the region doesn’t scroll like a real terminal when you reach the bottom. (It can’t since the page doesn’t necessarily store the whole region’s contents.)

Any sequences of the form \0ZZZ written to the region represent an HFB colour and change the current HFB colour. Generate one of these sequences using a string escape sequence (e.g. "\0099" to set default fg/bg), or else by using the HFB type which allows encoding any 16-bit HFB value.

Implementations§

Source§

impl Region<'_>

Source

pub fn full(&mut self) -> Region<'_>

Generate a child region the same size as this one. This is like a clone of the region except that it borrows from the parent region whilst it exists.

Examples found in repository?
examples/fontedit/app.rs (line 233)
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(&mut self, y: i32, x: i32, sy: i32, sx: i32) -> Region<'_>

Generate a child region that may be any size, inside or outside this region. When drawn to, only the part of the child region that overlaps this region (and all its parent regions) will be affected.

Examples found in repository?
examples/teeclub/draw.rs (line 10)
9pub fn card(r: &mut Region, y: i32, x: i32, card: Card) {
10    let mut r = r.region(y, x, CARD_SY, CARD_SX);
11    r.hfb(card.hfb).clear_all();
12    r.at(0, 2);
13    write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
14    r.at(CARD_SY - 1, 2);
15    write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
16}
17
18/// Draw the back of a card
19pub fn card_back(r: &mut Region, y: i32, x: i32) {
20    let mut r = r.region(y, x, CARD_SY, CARD_SX);
21    r.hfb(27);
22    for y in 0..CARD_SY {
23        r.at(y, 0).text("||||||");
24    }
25}
26
27/// Draw the space where a card could go
28pub fn card_space(r: &mut Region, y: i32, x: i32) {
29    let mut r = r.region(y, x, CARD_SY, CARD_SX);
30    r.hfb(71).clear_all();
31}
32
33/// Draw "more cards" indicator
34pub fn card_dots(r: &mut Region, y: i32, x: i32) {
35    let mut r = r.region(y, x, CARD_SY, CARD_SX);
36    r.hfb(70);
37    for y in 0..CARD_SY {
38        r.at(y, 0).text("::::::");
39    }
40}
More examples
Hide additional examples
examples/fontedit/app.rs (line 137)
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    }
Source

pub fn region_inplace(&mut self, y: i32, x: i32, sy: i32, sx: i32)

Replace this region with a child region that may be any size, inside or outside the old region. When drawn to, only the part of the new region that overlaps the old region (and all its parent regions) will be affected.

Source

pub fn sizer(&self) -> Sizer

Get the active Sizer

Source

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

Get the region size

Source

pub fn sy(&self) -> i32

Get the region size-Y, i.e. rows

Examples found in repository?
examples/fontedit/app.rs (line 222)
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    }
More examples
Hide additional examples
examples/teeclub/main.rs (line 179)
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    }
Source

pub fn sx(&self) -> i32

Get the region size-X, i.e. columns

Examples found in repository?
examples/teeclub/main.rs (line 228)
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    }
More examples
Hide additional examples
examples/fontedit/app.rs (line 170)
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 get_y(&self) -> i32

Get the current write row (Y-position)

Source

pub fn get_x(&self) -> i32

Get the current write column (X-position)

Examples found in repository?
examples/test2.rs (line 116)
97    fn redraw(&mut self, cx: CX![], full: bool) {
98        let pg = &mut self.page;
99        let sx = pg.sx();
100        let sy = pg.sy();
101
102        if sy < 24 || sx < 80 {
103            let mut r = pg.full();
104            r.clear_all_99();
105            r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106            write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107            return;
108        }
109
110        const TEXT: &str = "This is a test.  ";
111        const TEXTLEN: usize = TEXT.len();
112        let mid = sx >> 1;
113        for y in 0..sy {
114            let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115            r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116            while r.get_x() < sx {
117                r.text(TEXT);
118            }
119            if self.spacing_on {
120                let mut x = (sx + sy) / 2 - y;
121                while x > 0 {
122                    x -= self.spacing
123                }
124                r.at(0, x);
125                while r.get_x() < sx {
126                    r.text("/");
127                    r.skip(self.spacing - 1);
128                }
129            }
130            let shift = y * 6 / sy;
131            for x in 0..sx {
132                r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133            }
134        }
135        pg.full()
136            .at(sy - 2, (sx - 40) >> 1)
137            .hfb(6)
138            .text("  PRESS ANY KEY TO CONTINUE, Ctrl-C TO END  ");
139
140        self.update(cx, full);
141    }
Source

pub fn is_visible(&self) -> bool

Check whether any part of this region is visible on the page

Examples found in repository?
examples/fontedit/app.rs (line 198)
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    }
Source

pub fn hfb(&mut self, hfb: u16) -> &mut Self

Change the current HFB

Examples found in repository?
examples/teeclub/draw.rs (line 11)
9pub fn card(r: &mut Region, y: i32, x: i32, card: Card) {
10    let mut r = r.region(y, x, CARD_SY, CARD_SX);
11    r.hfb(card.hfb).clear_all();
12    r.at(0, 2);
13    write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
14    r.at(CARD_SY - 1, 2);
15    write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
16}
17
18/// Draw the back of a card
19pub fn card_back(r: &mut Region, y: i32, x: i32) {
20    let mut r = r.region(y, x, CARD_SY, CARD_SX);
21    r.hfb(27);
22    for y in 0..CARD_SY {
23        r.at(y, 0).text("||||||");
24    }
25}
26
27/// Draw the space where a card could go
28pub fn card_space(r: &mut Region, y: i32, x: i32) {
29    let mut r = r.region(y, x, CARD_SY, CARD_SX);
30    r.hfb(71).clear_all();
31}
32
33/// Draw "more cards" indicator
34pub fn card_dots(r: &mut Region, y: i32, x: i32) {
35    let mut r = r.region(y, x, CARD_SY, CARD_SX);
36    r.hfb(70);
37    for y in 0..CARD_SY {
38        r.at(y, 0).text("::::::");
39    }
40}
More examples
Hide additional examples
examples/teeclub/main.rs (line 176)
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    }
examples/test2.rs (line 105)
97    fn redraw(&mut self, cx: CX![], full: bool) {
98        let pg = &mut self.page;
99        let sx = pg.sx();
100        let sy = pg.sy();
101
102        if sy < 24 || sx < 80 {
103            let mut r = pg.full();
104            r.clear_all_99();
105            r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106            write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107            return;
108        }
109
110        const TEXT: &str = "This is a test.  ";
111        const TEXTLEN: usize = TEXT.len();
112        let mid = sx >> 1;
113        for y in 0..sy {
114            let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115            r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116            while r.get_x() < sx {
117                r.text(TEXT);
118            }
119            if self.spacing_on {
120                let mut x = (sx + sy) / 2 - y;
121                while x > 0 {
122                    x -= self.spacing
123                }
124                r.at(0, x);
125                while r.get_x() < sx {
126                    r.text("/");
127                    r.skip(self.spacing - 1);
128                }
129            }
130            let shift = y * 6 / sy;
131            for x in 0..sx {
132                r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133            }
134        }
135        pg.full()
136            .at(sy - 2, (sx - 40) >> 1)
137            .hfb(6)
138            .text("  PRESS ANY KEY TO CONTINUE, Ctrl-C TO END  ");
139
140        self.update(cx, full);
141    }
examples/fontedit/app.rs (line 109)
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    }
131
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    }
239
240    fn draw_all(&mut self, cx: CX![]) {
241        self.draw_cells(cx);
242        self.draw_mini(cx);
243        self.draw_param(cx);
244    }
245
246    fn input(&mut self, cx: CX![], key: Key) {
247        match key {
248            Key::Ctrl('L') => self.redraw(cx),
249            Key::Ctrl('C') => stop!(cx),
250            Key::Left => {
251                if self.cx > 0 {
252                    self.move_or_draw(cx, 0, -1)
253                }
254            }
255            Key::Right => {
256                if self.cx + 1 < self.font.sx {
257                    self.move_or_draw(cx, 0, 1)
258                }
259            }
260            Key::Up => {
261                if self.cy > 0 {
262                    self.move_or_draw(cx, -1, 0)
263                }
264            }
265            Key::Down => {
266                if self.cy + 1 < self.font.sy {
267                    self.move_or_draw(cx, 1, 0)
268                }
269            }
270            Key::PgUp => {
271                if self.index > 0 {
272                    self.index -= 1;
273                    self.draw_all(cx);
274                }
275            }
276            Key::PgDn => {
277                if self.index + 1 < self.font.index_limit() {
278                    self.index += 1;
279                    self.draw_all(cx);
280                }
281            }
282            Key::Home => {
283                self.index = 0;
284                self.draw_all(cx);
285            }
286            Key::End => {
287                self.index = self.font.index_limit() - 1;
288                self.draw_all(cx);
289            }
290            Key::Pr(' ') => {
291                self.undo_push();
292                self.unsaved = true;
293                let curr = self.font.get(self.index, self.cy, self.cx);
294                self.font.set(self.index, self.cy, self.cx, !curr);
295                self.draw_all(cx);
296            }
297            Key::Pr('d') => {
298                self.mode = match self.mode {
299                    Mode::Draw => Mode::Move,
300                    _ => Mode::Draw,
301                };
302                self.draw_all(cx);
303            }
304            Key::Pr('r') => {
305                self.mode = match self.mode {
306                    Mode::Roll => Mode::Move,
307                    _ => Mode::Roll,
308                };
309                self.draw_all(cx);
310            }
311            Key::Pr('f') => {
312                self.undo_push();
313                self.unsaved = true;
314                self.font.fill(self.index, self.cy, self.cx, true, true);
315                self.draw_all(cx);
316            }
317            Key::Pr('h') => {
318                self.undo_push();
319                self.unsaved = true;
320                self.font.fill(self.index, self.cy, self.cx, false, true);
321                self.draw_all(cx);
322            }
323            Key::Pr('v') => {
324                self.undo_push();
325                self.unsaved = true;
326                self.font.fill(self.index, self.cy, self.cx, true, false);
327                self.draw_all(cx);
328            }
329            Key::Pr('u') => {
330                if let Some(font) = self.undo.pop_front() {
331                    self.font = font;
332                    let limit = self.font.index_limit();
333                    self.index = self.index.min(limit - 1);
334                    self.draw_all(cx);
335                }
336            }
337            Key::Pr('l') => {
338                self.font.rule_bm ^= 1 << self.cy;
339                self.unsaved = true;
340                self.draw_all(cx);
341            }
342            Key::Pr('n') => {
343                self.undo_push();
344                self.unsaved = true;
345                self.index = self.font.index_limit();
346                self.font.add_glyph();
347                self.draw_param(cx);
348                self.draw_cells(cx);
349            }
350            Key::Pr('c') => {
351                self.clip = self.font.get_glyph(self.index);
352            }
353            Key::Pr('P') => {
354                if !self.clip.is_empty() {
355                    self.undo_push();
356                    self.unsaved = true;
357                    self.font.set_glyph(self.index, &self.clip);
358                    self.draw_all(cx);
359                }
360            }
361            Key::Pr('S') => {
362                if let Err(e) = self.font.save() {
363                    self.save_error = Some(e.to_string());
364                } else {
365                    self.save_error = None;
366                    self.unsaved = false;
367                }
368                self.draw_param(cx);
369            }
370            Key::Pr('Q') => {
371                if let Err(e) = self.font.save() {
372                    fail!(cx, e);
373                } else {
374                    stop!(cx);
375                }
376            }
377            _ => call!([self.term], bell()),
378        }
379    }
380
381    fn move_or_draw(&mut self, cx: CX![], dy: i32, dx: i32) {
382        match self.mode {
383            Mode::Move => {
384                self.cx = (self.cx as i32 + dx) as usize;
385                self.cy = (self.cy as i32 + dy) as usize;
386                self.draw_cells(cx);
387            }
388            Mode::Draw => {
389                self.undo_push();
390                self.unsaved = true;
391                let curr = self.font.get(self.index, self.cy, self.cx);
392                self.cx = (self.cx as i32 + dx) as usize;
393                self.cy = (self.cy as i32 + dy) as usize;
394                self.font.set(self.index, self.cy, self.cx, curr);
395                self.draw_all(cx);
396            }
397            Mode::Roll => {
398                self.undo_push();
399                if dx != 0 {
400                    self.font.roll_horiz(self.index, dx);
401                }
402                if dy != 0 {
403                    self.font.roll_vert(self.index, dy);
404                }
405                self.draw_all(cx);
406            }
407        }
408    }
409
410    fn undo_push(&mut self) {
411        self.undo.push_front(self.font.clone());
412        while self.undo.len() > 50 {
413            self.undo.pop_back();
414        }
415    }
416}
417
418fn draw_cell(
419    font: &Font,
420    mut r: Region,
421    index: usize,
422    black: u16,
423    white: u16,
424    curs: Option<(usize, usize, Mode)>,
425) {
426    let form = &font.chars[index].form;
427    for y in 0..font.sy {
428        let rule = ((font.rule_bm >> y) & 1) != 0;
429        for x in 0..font.sx {
430            r.hfb(if form[x + y * font.sx] { white } else { black });
431            let mut text = if rule { "--" } else { "  " };
432            if let Some((cy, cx, mode)) = curs
433                && cy == y
434                && cx == x
435            {
436                text = mode.cursor();
437            }
438            r.text(text);
439        }
440    }
441}
Source

pub fn at(&mut self, wy: i32, wx: i32) -> &mut Self

Change the write position to the given position relative to region top-left.

Examples found in repository?
examples/teeclub/draw.rs (line 12)
9pub fn card(r: &mut Region, y: i32, x: i32, card: Card) {
10    let mut r = r.region(y, x, CARD_SY, CARD_SX);
11    r.hfb(card.hfb).clear_all();
12    r.at(0, 2);
13    write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
14    r.at(CARD_SY - 1, 2);
15    write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
16}
17
18/// Draw the back of a card
19pub fn card_back(r: &mut Region, y: i32, x: i32) {
20    let mut r = r.region(y, x, CARD_SY, CARD_SX);
21    r.hfb(27);
22    for y in 0..CARD_SY {
23        r.at(y, 0).text("||||||");
24    }
25}
26
27/// Draw the space where a card could go
28pub fn card_space(r: &mut Region, y: i32, x: i32) {
29    let mut r = r.region(y, x, CARD_SY, CARD_SX);
30    r.hfb(71).clear_all();
31}
32
33/// Draw "more cards" indicator
34pub fn card_dots(r: &mut Region, y: i32, x: i32) {
35    let mut r = r.region(y, x, CARD_SY, CARD_SX);
36    r.hfb(70);
37    for y in 0..CARD_SY {
38        r.at(y, 0).text("::::::");
39    }
40}
More examples
Hide additional examples
examples/teeclub/main.rs (line 182)
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    }
examples/fontedit/app.rs (line 160)
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    }
examples/test2.rs (line 105)
97    fn redraw(&mut self, cx: CX![], full: bool) {
98        let pg = &mut self.page;
99        let sx = pg.sx();
100        let sy = pg.sy();
101
102        if sy < 24 || sx < 80 {
103            let mut r = pg.full();
104            r.clear_all_99();
105            r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106            write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107            return;
108        }
109
110        const TEXT: &str = "This is a test.  ";
111        const TEXTLEN: usize = TEXT.len();
112        let mid = sx >> 1;
113        for y in 0..sy {
114            let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115            r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116            while r.get_x() < sx {
117                r.text(TEXT);
118            }
119            if self.spacing_on {
120                let mut x = (sx + sy) / 2 - y;
121                while x > 0 {
122                    x -= self.spacing
123                }
124                r.at(0, x);
125                while r.get_x() < sx {
126                    r.text("/");
127                    r.skip(self.spacing - 1);
128                }
129            }
130            let shift = y * 6 / sy;
131            for x in 0..sx {
132                r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133            }
134        }
135        pg.full()
136            .at(sy - 2, (sx - 40) >> 1)
137            .hfb(6)
138            .text("  PRESS ANY KEY TO CONTINUE, Ctrl-C TO END  ");
139
140        self.update(cx, full);
141    }
Source

pub fn char(&mut self, ch: char) -> &mut Self

Add a char to the region. This should represent a single glyph. Glyphs using combining characters can’t be added this way.

Examples found in repository?
examples/teeclub/main.rs (line 234)
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    }
Source

pub fn spaces(&mut self, n: i32) -> &mut Self

Add N spaces to the region.

Source

pub fn repl_char(&mut self) -> &mut Self

Add the Unicode replacement character U+FFFD

Source

pub fn origin(&mut self) -> &mut Self

Move write position to the region top-left

Source

pub fn skip(&mut self, n: i32) -> &mut Self

Skip the write position N cells forwards without overwriting any characters

Examples found in repository?
examples/test2.rs (line 127)
97    fn redraw(&mut self, cx: CX![], full: bool) {
98        let pg = &mut self.page;
99        let sx = pg.sx();
100        let sy = pg.sy();
101
102        if sy < 24 || sx < 80 {
103            let mut r = pg.full();
104            r.clear_all_99();
105            r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106            write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107            return;
108        }
109
110        const TEXT: &str = "This is a test.  ";
111        const TEXTLEN: usize = TEXT.len();
112        let mid = sx >> 1;
113        for y in 0..sy {
114            let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115            r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116            while r.get_x() < sx {
117                r.text(TEXT);
118            }
119            if self.spacing_on {
120                let mut x = (sx + sy) / 2 - y;
121                while x > 0 {
122                    x -= self.spacing
123                }
124                r.at(0, x);
125                while r.get_x() < sx {
126                    r.text("/");
127                    r.skip(self.spacing - 1);
128                }
129            }
130            let shift = y * 6 / sy;
131            for x in 0..sx {
132                r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133            }
134        }
135        pg.full()
136            .at(sy - 2, (sx - 40) >> 1)
137            .hfb(6)
138            .text("  PRESS ANY KEY TO CONTINUE, Ctrl-C TO END  ");
139
140        self.update(cx, full);
141    }
Source

pub fn newline(&mut self) -> &mut Self

Move write position to the start of the next row. Note that writing a full line leaves the write position just past the end of the line, but not on the next line, so calling this method correctly moves to the next line in that case.

Source

pub fn clear_all_99(&mut self) -> &mut Self

Clear the whole region to the default colour-pair (99). The write position is set to top-left, and the current colour is set to 99.

Examples found in repository?
examples/teeclub/main.rs (line 128)
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/test2.rs (line 104)
97    fn redraw(&mut self, cx: CX![], full: bool) {
98        let pg = &mut self.page;
99        let sx = pg.sx();
100        let sy = pg.sy();
101
102        if sy < 24 || sx < 80 {
103            let mut r = pg.full();
104            r.clear_all_99();
105            r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106            write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107            return;
108        }
109
110        const TEXT: &str = "This is a test.  ";
111        const TEXTLEN: usize = TEXT.len();
112        let mid = sx >> 1;
113        for y in 0..sy {
114            let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115            r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116            while r.get_x() < sx {
117                r.text(TEXT);
118            }
119            if self.spacing_on {
120                let mut x = (sx + sy) / 2 - y;
121                while x > 0 {
122                    x -= self.spacing
123                }
124                r.at(0, x);
125                while r.get_x() < sx {
126                    r.text("/");
127                    r.skip(self.spacing - 1);
128                }
129            }
130            let shift = y * 6 / sy;
131            for x in 0..sx {
132                r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133            }
134        }
135        pg.full()
136            .at(sy - 2, (sx - 40) >> 1)
137            .hfb(6)
138            .text("  PRESS ANY KEY TO CONTINUE, Ctrl-C TO END  ");
139
140        self.update(cx, full);
141    }
examples/fontedit/app.rs (line 171)
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 clear_all(&mut self) -> &mut Self

Clear the whole region to space characters of the current hfb colour-pair. This will be clipped according to the current and parent regions. The write position is set to top-left, and the current colour is set to hfb.

Examples found in repository?
examples/teeclub/draw.rs (line 11)
9pub fn card(r: &mut Region, y: i32, x: i32, card: Card) {
10    let mut r = r.region(y, x, CARD_SY, CARD_SX);
11    r.hfb(card.hfb).clear_all();
12    r.at(0, 2);
13    write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
14    r.at(CARD_SY - 1, 2);
15    write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
16}
17
18/// Draw the back of a card
19pub fn card_back(r: &mut Region, y: i32, x: i32) {
20    let mut r = r.region(y, x, CARD_SY, CARD_SX);
21    r.hfb(27);
22    for y in 0..CARD_SY {
23        r.at(y, 0).text("||||||");
24    }
25}
26
27/// Draw the space where a card could go
28pub fn card_space(r: &mut Region, y: i32, x: i32) {
29    let mut r = r.region(y, x, CARD_SY, CARD_SX);
30    r.hfb(71).clear_all();
31}
More examples
Hide additional examples
examples/teeclub/main.rs (line 176)
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    }
examples/fontedit/app.rs (line 109)
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    }
131
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    }
Source

pub fn clear_eol_99(&mut self) -> &mut Self

Clear to end-of-line with the default colour-pair. Leaves the current colour-pair set to 99.

Source

pub fn clear_eol(&mut self) -> &mut Self

Clear to end-of-line with the current colour-pair.

Source

pub fn text(&mut self, text: &str) -> &mut Self

Write some text at the current location with the current HFB, wrapping to the start of the next line at each row-end of the region. What is actually output to the page will be clipped according to all current and parent regions. Interprets \0ZZZ colour sequences to change the current HFB. Interprets \n.

Note that even if the text is partially or fully outside the clip region, the write position will still be advanced correctly.

Examples found in repository?
examples/teeclub/draw.rs (line 23)
19pub fn card_back(r: &mut Region, y: i32, x: i32) {
20    let mut r = r.region(y, x, CARD_SY, CARD_SX);
21    r.hfb(27);
22    for y in 0..CARD_SY {
23        r.at(y, 0).text("||||||");
24    }
25}
26
27/// Draw the space where a card could go
28pub fn card_space(r: &mut Region, y: i32, x: i32) {
29    let mut r = r.region(y, x, CARD_SY, CARD_SX);
30    r.hfb(71).clear_all();
31}
32
33/// Draw "more cards" indicator
34pub fn card_dots(r: &mut Region, y: i32, x: i32) {
35    let mut r = r.region(y, x, CARD_SY, CARD_SX);
36    r.hfb(70);
37    for y in 0..CARD_SY {
38        r.at(y, 0).text("::::::");
39    }
40}
More examples
Hide additional examples
examples/teeclub/main.rs (line 177)
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    }
examples/test2.rs (line 117)
97    fn redraw(&mut self, cx: CX![], full: bool) {
98        let pg = &mut self.page;
99        let sx = pg.sx();
100        let sy = pg.sy();
101
102        if sy < 24 || sx < 80 {
103            let mut r = pg.full();
104            r.clear_all_99();
105            r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106            write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107            return;
108        }
109
110        const TEXT: &str = "This is a test.  ";
111        const TEXTLEN: usize = TEXT.len();
112        let mid = sx >> 1;
113        for y in 0..sy {
114            let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115            r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116            while r.get_x() < sx {
117                r.text(TEXT);
118            }
119            if self.spacing_on {
120                let mut x = (sx + sy) / 2 - y;
121                while x > 0 {
122                    x -= self.spacing
123                }
124                r.at(0, x);
125                while r.get_x() < sx {
126                    r.text("/");
127                    r.skip(self.spacing - 1);
128                }
129            }
130            let shift = y * 6 / sy;
131            for x in 0..sx {
132                r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133            }
134        }
135        pg.full()
136            .at(sy - 2, (sx - 40) >> 1)
137            .hfb(6)
138            .text("  PRESS ANY KEY TO CONTINUE, Ctrl-C TO END  ");
139
140        self.update(cx, full);
141    }
examples/fontedit/app.rs (line 120)
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    }
131
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    }
239
240    fn draw_all(&mut self, cx: CX![]) {
241        self.draw_cells(cx);
242        self.draw_mini(cx);
243        self.draw_param(cx);
244    }
245
246    fn input(&mut self, cx: CX![], key: Key) {
247        match key {
248            Key::Ctrl('L') => self.redraw(cx),
249            Key::Ctrl('C') => stop!(cx),
250            Key::Left => {
251                if self.cx > 0 {
252                    self.move_or_draw(cx, 0, -1)
253                }
254            }
255            Key::Right => {
256                if self.cx + 1 < self.font.sx {
257                    self.move_or_draw(cx, 0, 1)
258                }
259            }
260            Key::Up => {
261                if self.cy > 0 {
262                    self.move_or_draw(cx, -1, 0)
263                }
264            }
265            Key::Down => {
266                if self.cy + 1 < self.font.sy {
267                    self.move_or_draw(cx, 1, 0)
268                }
269            }
270            Key::PgUp => {
271                if self.index > 0 {
272                    self.index -= 1;
273                    self.draw_all(cx);
274                }
275            }
276            Key::PgDn => {
277                if self.index + 1 < self.font.index_limit() {
278                    self.index += 1;
279                    self.draw_all(cx);
280                }
281            }
282            Key::Home => {
283                self.index = 0;
284                self.draw_all(cx);
285            }
286            Key::End => {
287                self.index = self.font.index_limit() - 1;
288                self.draw_all(cx);
289            }
290            Key::Pr(' ') => {
291                self.undo_push();
292                self.unsaved = true;
293                let curr = self.font.get(self.index, self.cy, self.cx);
294                self.font.set(self.index, self.cy, self.cx, !curr);
295                self.draw_all(cx);
296            }
297            Key::Pr('d') => {
298                self.mode = match self.mode {
299                    Mode::Draw => Mode::Move,
300                    _ => Mode::Draw,
301                };
302                self.draw_all(cx);
303            }
304            Key::Pr('r') => {
305                self.mode = match self.mode {
306                    Mode::Roll => Mode::Move,
307                    _ => Mode::Roll,
308                };
309                self.draw_all(cx);
310            }
311            Key::Pr('f') => {
312                self.undo_push();
313                self.unsaved = true;
314                self.font.fill(self.index, self.cy, self.cx, true, true);
315                self.draw_all(cx);
316            }
317            Key::Pr('h') => {
318                self.undo_push();
319                self.unsaved = true;
320                self.font.fill(self.index, self.cy, self.cx, false, true);
321                self.draw_all(cx);
322            }
323            Key::Pr('v') => {
324                self.undo_push();
325                self.unsaved = true;
326                self.font.fill(self.index, self.cy, self.cx, true, false);
327                self.draw_all(cx);
328            }
329            Key::Pr('u') => {
330                if let Some(font) = self.undo.pop_front() {
331                    self.font = font;
332                    let limit = self.font.index_limit();
333                    self.index = self.index.min(limit - 1);
334                    self.draw_all(cx);
335                }
336            }
337            Key::Pr('l') => {
338                self.font.rule_bm ^= 1 << self.cy;
339                self.unsaved = true;
340                self.draw_all(cx);
341            }
342            Key::Pr('n') => {
343                self.undo_push();
344                self.unsaved = true;
345                self.index = self.font.index_limit();
346                self.font.add_glyph();
347                self.draw_param(cx);
348                self.draw_cells(cx);
349            }
350            Key::Pr('c') => {
351                self.clip = self.font.get_glyph(self.index);
352            }
353            Key::Pr('P') => {
354                if !self.clip.is_empty() {
355                    self.undo_push();
356                    self.unsaved = true;
357                    self.font.set_glyph(self.index, &self.clip);
358                    self.draw_all(cx);
359                }
360            }
361            Key::Pr('S') => {
362                if let Err(e) = self.font.save() {
363                    self.save_error = Some(e.to_string());
364                } else {
365                    self.save_error = None;
366                    self.unsaved = false;
367                }
368                self.draw_param(cx);
369            }
370            Key::Pr('Q') => {
371                if let Err(e) = self.font.save() {
372                    fail!(cx, e);
373                } else {
374                    stop!(cx);
375                }
376            }
377            _ => call!([self.term], bell()),
378        }
379    }
380
381    fn move_or_draw(&mut self, cx: CX![], dy: i32, dx: i32) {
382        match self.mode {
383            Mode::Move => {
384                self.cx = (self.cx as i32 + dx) as usize;
385                self.cy = (self.cy as i32 + dy) as usize;
386                self.draw_cells(cx);
387            }
388            Mode::Draw => {
389                self.undo_push();
390                self.unsaved = true;
391                let curr = self.font.get(self.index, self.cy, self.cx);
392                self.cx = (self.cx as i32 + dx) as usize;
393                self.cy = (self.cy as i32 + dy) as usize;
394                self.font.set(self.index, self.cy, self.cx, curr);
395                self.draw_all(cx);
396            }
397            Mode::Roll => {
398                self.undo_push();
399                if dx != 0 {
400                    self.font.roll_horiz(self.index, dx);
401                }
402                if dy != 0 {
403                    self.font.roll_vert(self.index, dy);
404                }
405                self.draw_all(cx);
406            }
407        }
408    }
409
410    fn undo_push(&mut self) {
411        self.undo.push_front(self.font.clone());
412        while self.undo.len() > 50 {
413            self.undo.pop_back();
414        }
415    }
416}
417
418fn draw_cell(
419    font: &Font,
420    mut r: Region,
421    index: usize,
422    black: u16,
423    white: u16,
424    curs: Option<(usize, usize, Mode)>,
425) {
426    let form = &font.chars[index].form;
427    for y in 0..font.sy {
428        let rule = ((font.rule_bm >> y) & 1) != 0;
429        for x in 0..font.sx {
430            r.hfb(if form[x + y * font.sx] { white } else { black });
431            let mut text = if rule { "--" } else { "  " };
432            if let Some((cy, cx, mode)) = curs
433                && cy == y
434                && cx == x
435            {
436                text = mode.cursor();
437            }
438            r.text(text);
439        }
440    }
441}
Source

pub fn bytes(&mut self, text: &[u8]) -> &mut Self

Write some text (expressed as bytes of UTF-8) at the current location with the current HFB, wrapping to the start of the next line at each row-end of the region. What is actually output to the page will be clipped according to all current and parent regions. Interprets \0ZZZ colour sequences to change the current HFB. Interprets \n.

Note that even if the text is partially or fully outside the clip region, the write position will still be advanced correctly.

Source

pub fn get(&mut self, y: i32, x: i32) -> Option<(u16, &[u8])>

Get the contents of the cell at the given location relative to the region’s top-left. Returns None if the location is outside the region’s clip. Otherwise returns Some((hfb, data)) where data is the UTF-8 data of the cell. Note that in the case of a double-width character, the data will be prefixed with an 0xFF byte in the left cell, and will contain an 0xFE byte alone in the right cell.

Source

pub fn set_hfb(&mut self, y: i32, x: i32, hfb: u16)

Set the HFB colour of a cell without affecting the glyph. Location is relative to region top-left.

Examples found in repository?
examples/test2.rs (line 132)
97    fn redraw(&mut self, cx: CX![], full: bool) {
98        let pg = &mut self.page;
99        let sx = pg.sx();
100        let sy = pg.sy();
101
102        if sy < 24 || sx < 80 {
103            let mut r = pg.full();
104            r.clear_all_99();
105            r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106            write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107            return;
108        }
109
110        const TEXT: &str = "This is a test.  ";
111        const TEXTLEN: usize = TEXT.len();
112        let mid = sx >> 1;
113        for y in 0..sy {
114            let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115            r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116            while r.get_x() < sx {
117                r.text(TEXT);
118            }
119            if self.spacing_on {
120                let mut x = (sx + sy) / 2 - y;
121                while x > 0 {
122                    x -= self.spacing
123                }
124                r.at(0, x);
125                while r.get_x() < sx {
126                    r.text("/");
127                    r.skip(self.spacing - 1);
128                }
129            }
130            let shift = y * 6 / sy;
131            for x in 0..sx {
132                r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133            }
134        }
135        pg.full()
136            .at(sy - 2, (sx - 40) >> 1)
137            .hfb(6)
138            .text("  PRESS ANY KEY TO CONTINUE, Ctrl-C TO END  ");
139
140        self.update(cx, full);
141    }
Source

pub fn set(&mut self, y: i32, x: i32, hfb: u16, data: &[u8])

Set the HFB colour and UTF-8 glyph data of a cell. Location is relative to region top-left. For double-width glyphs, the left cell should contain byte 0xFF plus the UTF-8, and the right cell should contain only an 0xFE byte. If one or other half is missing, the incorrect cells will show as the replacement character.

Source

pub fn braille_plot(&mut self, py: i32, px: i32, hfb: u16, set: bool)

Plot or unplot a point using Braille glyphs. Braille glyphs contain 2 points across and 4 down. They can be used for lo-res graphics. The total grid available to be addressed by (py, px) is 4*sy high and 2*sx wide. The only restriction is that the HFB colour-pair cannot be set independently for each point. Those apply at the glyph level. On plotting, if the point being plotted belongs to a non-Braille glyph, then that glyph is overwritten. If it is a Braille glyph then the point is added to it (set == true) or removed from it (set == false).

Examples found in repository?
examples/fontedit/app.rs (line 449)
443fn draw_mini_cell(font: &Font, mut r: Region, oy: i32, ox: i32, index: usize, hfb: u16) {
444    if index < font.chars.len() {
445        let form = &font.chars[index].form;
446        for y in 0..font.sy {
447            for x in 0..font.sx {
448                if form[x + y * font.sx] {
449                    r.braille_plot(oy + y as i32, ox + x as i32, hfb, true);
450                }
451            }
452        }
453    }
454}

Trait Implementations§

Source§

impl Debug for Region<'_>

Source§

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

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

impl Write for Region<'_>

Source§

fn flush(&mut self) -> Result<()>

The data is considered already flushed as soon as it is written to the region.

Source§

fn write(&mut self, buf: &[u8]) -> Result<usize>

Writes a buffer into this writer, returning how many bytes were written. Read more
1.36.0 · Source§

fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize, Error>

Like write, except that it writes from a slice of buffers. Read more
Source§

fn is_write_vectored(&self) -> bool

🔬This is a nightly-only experimental API. (can_vector)
Determines if this Writer has an efficient write_vectored implementation. Read more
1.0.0 · Source§

fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>

Attempts to write an entire buffer into this writer. Read more
Source§

fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>

🔬This is a nightly-only experimental API. (write_all_vectored)
Attempts to write multiple buffers into this writer. Read more
1.0.0 · Source§

fn write_fmt(&mut self, args: Arguments<'_>) -> Result<(), Error>

Writes a formatted string into this writer, returning any error encountered. Read more
1.0.0 · Source§

fn by_ref(&mut self) -> &mut Self
where Self: Sized,

Creates a “by reference” adapter for this instance of Write. Read more

Auto Trait Implementations§

§

impl<'a> Freeze for Region<'a>

§

impl<'a> !RefUnwindSafe for Region<'a>

§

impl<'a> !Send for Region<'a>

§

impl<'a> !Sync for Region<'a>

§

impl<'a> Unpin for Region<'a>

§

impl<'a> UnsafeUnpin for Region<'a>

§

impl<'a> !UnwindSafe for Region<'a>

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.