Skip to main content

termgrid_core/
ops.rs

1use crate::{
2    style::is_plain_style,
3    text::{is_default_wrap_opts, Span, WrapOpts},
4    Style,
5};
6use serde::{Deserialize, Serialize};
7
8fn is_default_charset(c: &BoxCharset) -> bool {
9    *c == BoxCharset::default()
10}
11
12/// A set of draw ops emitted by a producer for a single render tick.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
14pub struct Frame {
15    pub ops: Vec<RenderOp>,
16}
17
18/// A single cell in a `blit` payload.
19///
20/// When present, this cell overwrites the destination.
21/// When absent (`null` in JSON), the destination cell is left unchanged.
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct BlitCell {
24    pub glyph: String,
25    #[serde(default, skip_serializing_if = "is_plain_style")]
26    pub style: Style,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
30#[serde(rename_all = "snake_case")]
31pub enum BoxCharset {
32    /// ASCII `+`, `-`, `|`.
33    Ascii,
34    /// Unicode single-line box drawing characters.
35    #[default]
36    UnicodeSingle,
37    /// Unicode double-line box drawing characters.
38    UnicodeDouble,
39}
40
41fn is_default_truncate_mode(m: &TruncateMode) -> bool {
42    *m == TruncateMode::default()
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
46#[serde(rename_all = "snake_case")]
47pub enum TruncateMode {
48    /// Drop any glyphs that do not fit.
49    #[default]
50    Clip,
51    /// Replace the tail with an ellipsis ("…") when truncation occurs.
52    Ellipsis,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(tag = "op", rename_all = "snake_case")]
57pub enum RenderOp {
58    /// Clear the entire grid to empty cells.
59    Clear,
60
61    /// Clear a full line (row) to plain spaces.
62    ///
63    /// This is rendered as plain spaces rather than `Cell::Empty` to avoid
64    /// style bleed from earlier styled cells on the same line.
65    ClearLine { y: u16 },
66
67    /// Clear from (`x`,`y`) to end-of-line (inclusive) to plain spaces.
68    ///
69    /// This mirrors ANSI EL (erase in line) mode 0.
70    ClearEol { x: u16, y: u16 },
71
72    /// Clear from start-of-line to (`x`,`y`) (inclusive) to plain spaces.
73    ///
74    /// This mirrors ANSI EL (erase in line) mode 1.
75    ClearBol { x: u16, y: u16 },
76
77    /// Clear from (`x`,`y`) to end-of-screen (inclusive) to plain spaces.
78    ///
79    /// This mirrors ANSI ED (erase in display) mode 0.
80    ClearEos { x: u16, y: u16 },
81
82    /// Clear a rectangle to plain spaces.
83    ///
84    /// This is semantically equivalent to `FillRect` with a plain style.
85    ClearRect { x: u16, y: u16, w: u16, h: u16 },
86
87    /// Put text at a coordinate.
88    Put {
89        x: u16,
90        y: u16,
91        text: String,
92        #[serde(default, skip_serializing_if = "is_plain_style")]
93        style: Style,
94    },
95
96    /// Put a single glyph (grapheme cluster) at a coordinate.
97    PutGlyph {
98        x: u16,
99        y: u16,
100        glyph: String,
101        #[serde(default, skip_serializing_if = "is_plain_style")]
102        style: Style,
103    },
104
105    /// Put a single-line label, clipped to `w` cells.
106    ///
107    /// This is a convenience op for common UI labels.
108    Label {
109        x: u16,
110        y: u16,
111        /// Maximum width in cells.
112        w: u16,
113        text: String,
114        #[serde(default, skip_serializing_if = "is_plain_style")]
115        style: Style,
116        #[serde(default, skip_serializing_if = "is_default_truncate_mode")]
117        truncate: TruncateMode,
118    },
119
120    /// Put a single-line styled label (spans), clipped to `w` cells.
121    ///
122    /// This op is analogous to `Label` but supports inline styling.
123    LabelStyled {
124        x: u16,
125        y: u16,
126        /// Maximum width in cells.
127        w: u16,
128        spans: Vec<Span>,
129        #[serde(default, skip_serializing_if = "is_default_truncate_mode")]
130        truncate: TruncateMode,
131    },
132
133    /// Put a multi-line block of text, wrapped and clipped within a rectangle.
134    ///
135    /// This is a higher-level convenience op.
136    TextBlock {
137        x: u16,
138        y: u16,
139        w: u16,
140        h: u16,
141        text: String,
142        #[serde(default, skip_serializing_if = "is_plain_style")]
143        style: Style,
144        #[serde(default, skip_serializing_if = "is_default_wrap_opts")]
145        wrap: WrapOpts,
146    },
147
148    /// Put a styled multi-line block (spans), wrapped and clipped within a rectangle.
149    TextBlockStyled {
150        x: u16,
151        y: u16,
152        w: u16,
153        h: u16,
154        spans: Vec<Span>,
155        #[serde(default, skip_serializing_if = "is_default_wrap_opts")]
156        wrap: WrapOpts,
157    },
158
159    /// Fill a rectangle with a single glyph.
160    FillRect {
161        x: u16,
162        y: u16,
163        w: u16,
164        h: u16,
165        glyph: String,
166        #[serde(default, skip_serializing_if = "is_plain_style")]
167        style: Style,
168    },
169
170    /// Draw a box (border) around a rectangle.
171    Box {
172        x: u16,
173        y: u16,
174        w: u16,
175        h: u16,
176        #[serde(default, skip_serializing_if = "is_default_charset")]
177        charset: BoxCharset,
178        #[serde(default, skip_serializing_if = "is_plain_style")]
179        style: Style,
180    },
181
182    /// Blit a rectangular payload of optional cells.
183    ///
184    /// `cells` is row-major with length `w*h`.
185    Blit {
186        x: u16,
187        y: u16,
188        w: u16,
189        h: u16,
190        cells: Vec<Option<BlitCell>>,
191    },
192}