Skip to content

Commit

Permalink
feat: add position, inset, and gap style props
Browse files Browse the repository at this point in the history
  • Loading branch information
ccbrown committed Sep 27, 2024
1 parent edd446b commit 98f2a52
Show file tree
Hide file tree
Showing 12 changed files with 293 additions and 57 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ forms, and more!

<img src="https://raw.githubusercontent.com/ccbrown/iocraft/refs/heads/main/examples/images/table.png" height=402 />
<img src="https://raw.githubusercontent.com/ccbrown/iocraft/refs/heads/main/examples/images/form.png" height=387 />
<img src="https://raw.githubusercontent.com/ccbrown/iocraft/refs/heads/main/examples/images/fullscreen.png" height=462 />
<img src="https://raw.githubusercontent.com/ccbrown/iocraft/refs/heads/main/examples/images/overlap.png" height=450 />
<img src="https://raw.githubusercontent.com/ccbrown/iocraft/refs/heads/main/examples/images/weather-powershell.png" height=350 />

## Shoutouts
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ To run any of the examples, use `cargo run --example NAME`. For example, to run
|[form.rs](./form.rs)<br />Displays a form prompting the user for input into multiple text fields. Uses mutable reference props to surface the user's input to the caller once the form is submitted.|![preview](./images/form.png)|
|[fullscreen.rs](./fullscreen.rs)<br />Takes over the full terminal, rendering to an alternate buffer and preventing the user from scrolling.|![preview](./images/fullscreen.png)|
|[hello_world.rs](./hello_world.rs)<br />Hello, world!|![preview](./images/hello-world.png)|
|[overlap.rs](./overlap.rs)<br />Uses absolute positioning to create overlapping elements.|![preview](./images/overlap.png)|
|[progress_bar.rs](./progress_bar.rs)<br />Renders a dynamic progress bar which fills up and then exits.|![preview](./images/progress_bar.png)|
|[table.rs](./table.rs)<br />Displays a list of users provided by reference via properties.|![preview](./images/table.png)|
|[use_input.rs](./use_input.rs)<br />Demonstrates using keyboard input to move a 👾.|![preview](./images/use_input.png)|
Expand Down
Binary file added examples/images/overlap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 51 additions & 0 deletions examples/overlap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use iocraft::prelude::*;

const LOREM_IPSUM: &str = "Lorem ipsum odor amet, consectetuer adipiscing elit. \
Lobortis hendrerit nec ipsum dapibus quam. Donec malesuada tincidunt elementum \
mollis vehicula quisque purus. Est volutpat integer, donec sagittis placerat \
fermentum phasellus ipsum sollicitudin. Tempus laoreet ad tempus aptent proin \
per donec lectus. Quisque auctor urna; phasellus urna tortor ligula. Class \
pharetra bibendum tristique, quisque consectetur placerat potenti. Imperdiet ut \
torquent vestibulum eleifend bibendum et. Dictumst vulputate interdum iaculis \
at conubia venenatis.";

fn main() {
element! {
Box(
border_style: BorderStyle::DoubleLeftRight,
border_color: Color::Green,
margin: 1,
width: 78,
flex_direction: FlexDirection::Column,
) {
Box(margin_top: -1) {
Text(content: " Overlap Example ", wrap: TextWrap::NoWrap)
}
Box(padding: 1) {
Text(content: format!("{} {}", LOREM_IPSUM, LOREM_IPSUM), color: Color::DarkGrey, weight: Weight::Light)
}
Box(
border_color: Color::Red,
border_style: BorderStyle::DoubleTopBottom,
padding: 1,
position: Position::Absolute,
top: 2,
left: 4,
) {
Text(content: "This element is overlapping the text!")
}
Box(
background_color: Color::Reset,
border_color: Color::Red,
border_style: BorderStyle::DoubleTopBottom,
padding: 1,
position: Position::Absolute,
top: 8,
left: 4,
) {
Text(content: "We can cover it up by setting a background color.")
}
}
}
.print();
}
5 changes: 0 additions & 5 deletions packages/iocraft-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,4 @@ uuid = { version = "1.10.0", features = ["v4"] }
[dev-dependencies]
iocraft = { path = "../iocraft" }
smol = "2.0.1"
chrono = "0.4.38"
unicode-width = "0.1.13"
anyhow = "1.0.89"
surf = { version = "2.3.2", default-features = false, features = ["h1-client"] }
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
52 changes: 52 additions & 0 deletions packages/iocraft-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,24 @@ pub fn with_layout_style_props(_attr: TokenStream, item: TokenStream) -> TokenSt
/// Sets the maximum height of the element.
pub max_height: ::iocraft::Size
},
quote! {
/// Defines the gaps in between rows or columns of flex items.
///
/// See [the MDN documentation for gap](https://developer.mozilla.org/en-US/docs/Web/CSS/gap).
pub gap: ::iocraft::Gap
},
quote! {
/// Defines the gaps in between columns of flex items.
///
/// See [the MDN documentation for column-gap](https://developer.mozilla.org/en-US/docs/Web/CSS/column-gap).
pub column_gap: ::iocraft::Gap
},
quote! {
/// Defines the gaps in between rows of flex items.
///
/// See [the MDN documentation for row-gap](https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap).
pub row_gap: ::iocraft::Gap
},
quote! {
/// Defines the area to reserve around the element's content, but inside the border.
///
Expand Down Expand Up @@ -533,6 +551,40 @@ pub fn with_layout_style_props(_attr: TokenStream, item: TokenStream) -> TokenSt
/// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
pub padding_left: ::iocraft::Padding
},
quote! {
/// Controls how the element is layed out and whether it will be controlled by the flexbox.
pub position: ::iocraft::Position
},
quote! {
/// Sets the position of a positioned element.
///
/// See [the MDN documentation for inset](https://developer.mozilla.org/en-US/docs/Web/CSS/inset).
pub inset: ::iocraft::Inset
},
quote! {
/// Sets the vertical position of a positioned element.
///
/// See [the MDN documentation for top](https://developer.mozilla.org/en-US/docs/Web/CSS/top).
pub top: ::iocraft::Inset
},
quote! {
/// Sets the horizontal position of a positioned element.
///
/// See [the MDN documentation for right](https://developer.mozilla.org/en-US/docs/Web/CSS/right).
pub right: ::iocraft::Inset
},
quote! {
/// Sets the vertical position of a positioned element.
///
/// See [the MDN documentation for bottom](https://developer.mozilla.org/en-US/docs/Web/CSS/bottom).
pub bottom: ::iocraft::Inset
},
quote! {
/// Sets the horizontal position of a positioned element.
///
/// See [the MDN documentation for left](https://developer.mozilla.org/en-US/docs/Web/CSS/left).
pub left: ::iocraft::Inset
},
quote! {
/// Defines the area to reserve around the element's content, but outside the border.
///
Expand Down
6 changes: 1 addition & 5 deletions packages/iocraft/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,10 @@ textwrap = "0.16.1"
generational-box = "0.5.6"
any_key = "0.1.1"
uuid = { version = "1.10.0", features = ["v4"] }
indexmap = "2.5.0"

[dev-dependencies]
indoc = "2"
smol = "2.0.1"
smol-macros = "0.1.1"
macro_rules_attribute = "0.2.0"
chrono = "0.4.38"
anyhow = "1.0.89"
surf = { version = "2.3.2", default-features = false, features = ["h1-client"] }
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
53 changes: 49 additions & 4 deletions packages/iocraft/src/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,25 @@ impl Canvas {
self.cells.len()
}

fn clear_text(&mut self, x: usize, y: usize, w: usize, h: usize) {
for y in y..y + h {
if let Some(row) = self.cells.get_mut(y) {
for x in x..x + w {
if x < row.len() {
row[x].character = None;
}
}
}
}
}

fn set_background_color(&mut self, x: usize, y: usize, w: usize, h: usize, color: Color) {
for y in y..y + h {
let row = &mut self.cells[y];
for x in x..x + w {
if x < row.len() {
row[x].background_color = Some(color);
if let Some(row) = self.cells.get_mut(y) {
for x in x..x + w {
if x < row.len() {
row[x].background_color = Some(color);
}
}
}
}
Expand Down Expand Up @@ -331,6 +344,26 @@ impl<'a> CanvasSubviewMut<'a> {
);
}

/// Removes text from the region.
pub fn clear_text(&mut self, x: isize, y: isize, w: usize, h: usize) {
let mut left = self.x as isize + x;
let mut top = self.y as isize + y;
let mut right = left + w as isize;
let mut bottom = top + h as isize;
if self.clip {
left = left.max(self.x as isize);
top = top.max(self.y as isize);
right = right.min((self.x + self.width) as isize);
bottom = bottom.min((self.y + self.height) as isize);
}
self.canvas.clear_text(
left as _,
top as _,
(right - left) as _,
(bottom - top) as _,
);
}

/// Writes text to the region.
pub fn set_text(&mut self, x: isize, mut y: isize, text: &str, style: CanvasTextStyle) {
let mut x = self.x as isize + x;
Expand Down Expand Up @@ -545,6 +578,18 @@ mod tests {
assert_eq!(actual, "\n\n ne 2\n ne 3\n\n");
}

#[test]
fn test_canvas_text_clearing() {
let mut canvas = Canvas::new(10, 1);
canvas
.subview_mut(0, 0, 10, 1, true)
.set_text(0, 0, "hello!", CanvasTextStyle::default());
assert_eq!(canvas.to_string(), "hello!\n");

canvas.subview_mut(0, 0, 10, 1, true).clear_text(0, 0, 3, 1);
assert_eq!(canvas.to_string(), " lo!\n");
}

#[test]
fn test_write_ansi_without_final_newline() {
let mut canvas = Canvas::new(10, 3);
Expand Down
4 changes: 2 additions & 2 deletions packages/iocraft/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use crate::{
render::{ComponentDrawer, ComponentUpdater, UpdateContext},
};
use futures::future::poll_fn;
use indexmap::IndexMap;
use std::{
any::{Any, TypeId},
collections::HashMap,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
Expand Down Expand Up @@ -211,7 +211,7 @@ impl InstantiatedComponent {

#[derive(Default)]
pub(crate) struct Components {
pub components: HashMap<ElementKey, InstantiatedComponent>,
pub components: IndexMap<ElementKey, InstantiatedComponent>,
}

impl Components {
Expand Down
60 changes: 60 additions & 0 deletions packages/iocraft/src/components/box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ impl Component for Box {
let mut canvas = drawer.canvas();

if let Some(color) = self.background_color {
canvas.clear_text(
0,
0,
layout.size.width as usize,
layout.size.height as usize,
);
canvas.set_background_color(
0,
0,
Expand Down Expand Up @@ -531,5 +537,59 @@ mod tests {
╰─────────────╯
"},
);

assert_eq!(
element! {
Box {
Text(content: "This is the background text.")
Box(
position: Position::Absolute,
top: 0,
left: 3,
) {
Text(content: "Foo!")
}
}
}
.to_string(),
"ThiFoo! the background text.\n",
);

assert_eq!(
element! {
Box {
Text(content: "This is the background text.")
Box(
position: Position::Absolute,
top: 0,
left: 3,
width: 6,
height: 1,
background_color: Color::Red,
)
}
}
.to_string(),
"Thi he background text.\n",
);

assert_eq!(
element! {
Box(width: 20, border_style: BorderStyle::Single, column_gap: 2) {
Box(width: 3) {
Text(content: "foo")
}
Box(width: 3) {
Text(content: "bar")
}
}
}
.to_string(),
indoc! {"
┌──────────────────┐
│foo bar │
└──────────────────┘
"},
);
}
}
8 changes: 4 additions & 4 deletions packages/iocraft/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ use futures::{
future::{select, FutureExt, LocalBoxFuture},
stream::{Stream, StreamExt},
};
use indexmap::IndexMap;
use std::{
any::Any,
cell::{Ref, RefMut},
collections::HashMap,
io, mem,
pin::Pin,
task::{self, Poll},
Expand Down Expand Up @@ -139,7 +139,7 @@ impl<'a, 'b, 'c> ComponentUpdater<'a, 'b, 'c> {
{
self.component_context_stack
.with_context(context, |component_context_stack| {
let mut used_components = HashMap::with_capacity(self.children.components.len());
let mut used_components = IndexMap::with_capacity(self.children.components.len());

let mut direct_child_node_ids = Vec::new();
let child_node_ids = if self.transparent_layout {
Expand All @@ -152,7 +152,7 @@ impl<'a, 'b, 'c> ComponentUpdater<'a, 'b, 'c> {

for mut child in children {
let mut component: InstantiatedComponent =
match self.children.components.remove(child.key()) {
match self.children.components.swap_remove(child.key()) {
Some(component)
if component.component().type_id()
== child.helper().component_type_id() =>
Expand Down Expand Up @@ -193,7 +193,7 @@ impl<'a, 'b, 'c> ComponentUpdater<'a, 'b, 'c> {
.set_children(self.node_id, &direct_child_node_ids)
.expect("we should be able to set the children");

for (_, component) in self.children.components.drain() {
for (_, component) in self.children.components.drain(..) {
self.context
.layout_engine
.remove(component.node_id())
Expand Down
Loading

0 comments on commit 98f2a52

Please sign in to comment.