Skip to main content

join_vertical

Function join_vertical 

Source
pub fn join_vertical(align: Position, surfaces: &[Surface]) -> Surface
Expand description

Join surfaces stacked vertically, aligning them horizontally.

All input surfaces are placed in a single column, aligned according to align.

§Example

use bobatea::surface::{Surface, join_vertical, Position};
let top    = Surface::from_text("ABC", Default::default());
let bottom = Surface::from_text("X", Default::default());
let combined = join_vertical(Position::Center, &[top, bottom]);
assert_eq!(combined.height(), 2);
Examples found in repository?
examples/hello_layout.rs (line 68)
37    fn render(&self, ctx: &mut Frame<'_>, _theme: &Theme) {
38        let area = ctx.area();
39        let buf = ctx.buffer_mut();
40
41        // Clear background
42        for y in area.top()..area.bottom() {
43            for x in area.left()..area.right() {
44                buf[(x, y)].reset();
45                buf[(x, y)].set_bg(Color::Black);
46            }
47        }
48
49        // Build some styled text surfaces
50        let hello = BobaStyle::new().fg(hex_color("#FF5F87")).bg(Color::Black).bold().render("Hello");
51
52        let world = BobaStyle::new().fg(hex_color("#A550DF")).bg(Color::Black).italic().render("World");
53
54        // Join them horizontally with a divider
55        let divider = BobaStyle::new().fg(Color::DarkGray).render("│");
56        let joined = join_horizontal(Position::Center, &[hello, divider, world]);
57
58        // Put a rounded border around it
59        let boxed = BobaStyle::new()
60            .border(Border::rounded())
61            .border_fg(hex_color("#874BFD"))
62            .padding(1, 2, 1, 2)
63            .render_surface(&joined);
64
65        // Add a small label below
66        let label = BobaStyle::new().fg(Color::DarkGray).margin_top(1).render("press 'q' to quit");
67
68        let doc = join_vertical(Position::Center, &[boxed, label]);
69
70        // Center the whole thing on screen
71        let centered = place(
72            area.width as usize,
73            area.height as usize,
74            Position::Center,
75            Position::Center,
76            &doc,
77            &Cell::blank(Style::default().bg(Color::Black)),
78        );
79
80        // Render into the frame buffer
81        centered.render_to_area(area, buf);
82    }
More examples
Hide additional examples
examples/layout.rs (line 178)
145    fn build_document(&self) -> Surface {
146        let has_dark_bg = has_dark_background();
147        let subtle = light_dark(has_dark_bg, hex_color("#D9DCCF"), hex_color("#383838"));
148        let highlight = light_dark(has_dark_bg, hex_color("#874BFD"), hex_color("#7D56F4"));
149        let special = light_dark(has_dark_bg, hex_color("#43BF6D"), hex_color("#73F59F"));
150
151        // Tabs
152        let tabs_row = self.tabs.render_to_surface();
153
154        // Title
155        let title_style = BobaStyle::new()
156            .margin_left(1)
157            .margin_right(5)
158            .padding(0, 1, 0, 1)
159            .italic()
160            .fg(hex_color("#FFF7DB"));
161        let title_colors = blend_2d(
162            1,
163            5,
164            hex_color("#F25D94"),
165            hex_color("#643AFF"),
166            hex_color("#EDFF82"),
167            hex_color("#14F9D5"),
168        );
169        let mut title_surfaces = Vec::new();
170        for (i, row) in title_colors.iter().enumerate() {
171            let color = row[0];
172            let s = title_style
173                .margin_left((i * 2) as u16)
174                .bg(color)
175                .render("Boba Tea");
176            title_surfaces.push(s);
177        }
178        let title = join_vertical(Position::Left, &title_surfaces);
179
180        let desc_style = BobaStyle::new().margin_top(1);
181        let info_style = BobaStyle::new()
182            .border(Border::normal().no_left().no_right().no_bottom())
183            .border_fg(subtle);
184        let divider = BobaStyle::new().padding(0, 1, 0, 1).fg(subtle).render("•");
185        let url = BobaStyle::new()
186            .fg(special)
187            .render("https://github.com/tascord/boba");
188        let info_content = join_horizontal(
189            Position::Top,
190            &[
191                BobaStyle::new().render("by flora (based on Charm)"),
192                divider,
193                url,
194            ],
195        );
196        let info = info_style.render_surface(&info_content);
197        let desc = join_vertical(
198            Position::Left,
199            &[
200                desc_style.render("Terminal layout + styling you won't hate."),
201                info,
202            ],
203        );
204        let title_row = join_horizontal(Position::Top, &[title, desc]);
205
206        // Dialog
207        let dialog_box = BobaStyle::new()
208            .border(Border::rounded())
209            .border_fg(hex_color("#874BFD"))
210            .padding(1, 4, 1, 4)
211            .align(Alignment::Center, Position::Center);
212
213        let question = BobaStyle::new().render_surface(&apply_gradient(
214            "Are you sure you want to eat marmalade?",
215            hex_color("#EDFF82"),
216            hex_color("#F25D94"),
217        ));
218        let ok_surf = self.ok_btn.render_to_surface(&Theme::default());
219        let cancel_surf = self.cancel_btn.render_to_surface(&Theme::default());
220        let buttons = join_horizontal(Position::Top, &[ok_surf, cancel_surf]);
221        let dialog_content = join_vertical(Position::Center, &[question, buttons]);
222        let dialog_inner = dialog_box.render_surface(&dialog_content);
223        let dialog = place_filled(
224            WIDTH,
225            DIALOG_HEIGHT,
226            Position::Center,
227            Position::Center,
228            &dialog_inner,
229            Style::default().fg(subtle),
230            "l o r e m ",
231        );
232        let mut dialog = dialog;
233        fit_width(
234            &mut dialog,
235            WIDTH,
236            &Cell::blank(Style::default().fg(subtle)),
237        );
238
239        // Color grid
240        let colors = color_grid_surf(14, 8);
241
242        // Lists
243        let list_header_style = BobaStyle::new()
244            .border(Border::normal().no_top().no_left().no_right())
245            .border_fg(subtle)
246            .margin_right(2);
247        let list_style = BobaStyle::new()
248            .border(Border::normal().no_top().no_bottom().no_right())
249            .border_fg(subtle)
250            .margin_right(1)
251            .height(8)
252            .width((WIDTH / 3) as u16);
253
254        let check = BobaStyle::new().fg(special).padding_right(1).render("✓");
255        let gray_done = light_dark(has_dark_bg, hex_color("#969B86"), hex_color("#696969"));
256        let list_done = |text: &str| -> Surface {
257            let body = BobaStyle::new().crossed_out().fg(gray_done).render(text);
258            join_horizontal(Position::Top, &[check.clone(), body])
259        };
260        let list_item = |text: &str| -> Surface { BobaStyle::new().padding_left(2).render(text) };
261
262        let list1 = join_vertical(
263            Position::Left,
264            &[
265                list_header_style.render("Citrus Fruits to Try"),
266                list_done("Grapefruit"),
267                list_done("Yuzu"),
268                list_item("Citron"),
269                list_item("Kumquat"),
270                list_item("Pomelo"),
271            ],
272        );
273        let list1 = list_style.render_surface(&list1);
274
275        let list2 = join_vertical(
276            Position::Left,
277            &[
278                list_header_style.render("Actual Boba Vendors"),
279                list_item("Chatime"),
280                list_item("Gong Cha"),
281                list_done("Teaser"),
282            ],
283        );
284        let list2 = list_style.render_surface(&list2);
285
286        let lists = join_horizontal(Position::Top, &[list1, list2, colors]);
287
288        // History
289        let history_style = BobaStyle::new()
290            .align(Alignment::Left, Position::Top)
291            .fg(hex_color("#FAFAFA"))
292            .bg(highlight)
293            .margin(1, HISTORY_GAP as u16, 0, 0)
294            .padding(1, 2, 1, 2)
295            .height(19)
296            .width(HISTORY_CONTENT_WIDTH as u16);
297
298        let history_a = history_style
299            .clone()
300            .align(Alignment::Right, Position::Top)
301            .render(LOREM);
302        let history_b = history_style
303            .clone()
304            .align(Alignment::Center, Position::Top)
305            .render(LOREM);
306        let history_c = history_style
307            .align(Alignment::Left, Position::Top)
308            .margin_right(0)
309            .render(LOREM);
310        let history = join_horizontal(Position::Top, &[history_a, history_b, history_c]);
311
312        // Status bar
313        let light_dark_state = if has_dark_bg { "Dark" } else { "Light" };
314        let bar_style = BobaStyle::new()
315            .fg(light_dark(
316                has_dark_bg,
317                hex_color("#343433"),
318                hex_color("#C1C6B2"),
319            ))
320            .bg(light_dark(
321                has_dark_bg,
322                hex_color("#D9DCCF"),
323                hex_color("#353533"),
324            ));
325
326        let status_key = status_seg(hex_color("#FF5F87")).render("STATUS");
327        let encoding = status_seg(hex_color("#A550DF"))
328            .align(Alignment::Right, Position::Center)
329            .render("UTF-8");
330        let fish = status_seg(hex_color("#6124DF")).render("Fish Cake");
331
332        // Segment widths already include their horizontal padding.
333        let used = status_key.width() + encoding.width() + fish.width();
334        let remaining = WIDTH.saturating_sub(used);
335        let status_text_width = remaining.saturating_sub(2);
336
337        let mut status_val = BobaStyle::new()
338            .inherit(bar_style)
339            .width(status_text_width as u16)
340            .padding_x(1)
341            .render(&format!("Ravishingly {}!", light_dark_state));
342        clip(&mut status_val, remaining);
343        fit_width(&mut status_val, remaining, &Cell::blank(*bar_style));
344
345        let bar = join_horizontal(Position::Top, &[status_key, status_val, encoding, fish]);
346        let mut status_bar = bar;
347        let fill_cell = Cell::blank(
348            Style::new()
349                .fg(light_dark(
350                    has_dark_bg,
351                    hex_color("#343433"),
352                    hex_color("#C1C6B2"),
353                ))
354                .bg(light_dark(
355                    has_dark_bg,
356                    hex_color("#D9DCCF"),
357                    hex_color("#353533"),
358                )),
359        );
360        fit_width(&mut status_bar, WIDTH, &fill_cell);
361
362        // Assemble document
363        join_vertical(
364            Position::Left,
365            &[tabs_row, title_row, dialog, lists, history, status_bar],
366        )
367    }