summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2022-01-30 21:33:20 -0500
committerClementTsang <cjhtsang@uwaterloo.ca>2022-01-30 22:58:00 -0500
commitef44e2cf328c75565ca110b566f0ab1b6432ff6e (patch)
treeec6a86d4e9db69a6d3652bf0624356eee6e51d0e
parentd0a0cc5e8c4b028ea5de3118985b71bd74b15be6 (diff)
tmp committuine
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml2
-rw-r--r--src/tuine/component/base/block.rs421
-rw-r--r--src/tuine/component/base/flex/mod.rs6
-rw-r--r--src/tuine/component/base/padding.rs16
-rw-r--r--src/tuine/component/base/shortcut.rs6
-rw-r--r--src/tuine/component/base/text_table/mod.rs4
-rw-r--r--src/tuine/component/base/time_graph.rs25
-rw-r--r--src/tuine/component/mod.rs3
-rw-r--r--src/tuine/context/context.rs35
-rw-r--r--src/tuine/context/mod.rs3
-rw-r--r--src/tuine/key.rs16
-rw-r--r--src/tuine/layout/size.rs2
-rw-r--r--src/tuine/mod.rs10
-rw-r--r--tests/invalid_config_tests.rs2
15 files changed, 449 insertions, 109 deletions
diff --git a/Cargo.lock b/Cargo.lock
index af64176f..ba3f7c2f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -247,6 +247,7 @@ dependencies = [
"float-ord",
"futures",
"futures-timer",
+ "gapbuffer",
"heim",
"indextree",
"itertools",
@@ -709,6 +710,12 @@ dependencies = [
]
[[package]]
+name = "gapbuffer"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e929b3ff01e4accdce7f5596a044890b5052ab7418ba8ce9ce5865d26ae4417"
+
+[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 7cf45e5b..25aeb90d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,6 @@ path = "src/bin/main.rs"
doc = false
[lib]
-test = false
doctest = false
doc = false
@@ -43,6 +42,7 @@ enum_dispatch = "0.3.7"
float-ord = "0.3.2"
futures = "0.3.14"
futures-timer = "3.0.2" # TODO: Remove?
+gapbuffer = "0.1.1"
indextree = "4.3.1" # TODO: Remove?
itertools = "0.10.0"
once_cell = "1.5.2"
diff --git a/src/tuine/component/base/block.rs b/src/tuine/component/base/block.rs
index 99776ffe..743a39d7 100644
--- a/src/tuine/component/base/block.rs
+++ b/src/tuine/component/base/block.rs
@@ -12,6 +12,13 @@ pub struct StyleSheet {
pub border: Style,
}
+struct BorderOffsets {
+ left: u16,
+ right: u16,
+ top: u16,
+ bottom: u16,
+}
+
/// A [`Block`] is a widget that draws a border around a child [`Component`], as well as optional
/// titles.
pub struct Block<Message, Child>
@@ -51,49 +58,30 @@ where
self
}
- fn inner_rect(&self, original: Rect) -> Rect {
- let mut inner = original;
-
- if self.borders.intersects(Borders::LEFT) {
- inner.x = inner.x.saturating_add(1).min(inner.right());
- inner.width = inner.width.saturating_sub(1);
- }
- if self.borders.intersects(Borders::TOP)
- || self.left_text.is_some()
- || self.right_text.is_some()
- {
- inner.y = inner.y.saturating_add(1).min(inner.bottom());
- inner.height = inner.height.saturating_sub(1);
- }
- if self.borders.intersects(Borders::RIGHT) {
- inner.width = inner.width.saturating_sub(1);
- }
- if self.borders.intersects(Borders::BOTTOM) {
- inner.height = inner.height.saturating_sub(1);
- }
- inner
+ pub fn borders(mut self, borders: Borders) -> Self {
+ self.borders = borders;
+ self
}
- fn outer_size(&self, original: Size) -> Size {
- let mut outer = original;
-
- if self.borders.intersects(Borders::LEFT) {
- outer.width = outer.width.saturating_add(1);
- }
- if self.borders.intersects(Borders::TOP)
- || self.left_text.is_some()
- || self.right_text.is_some()
- {
- outer.height = outer.height.saturating_add(1);
- }
- if self.borders.intersects(Borders::RIGHT) {
- outer.width = outer.width.saturating_add(1);
- }
- if self.borders.intersects(Borders::BOTTOM) {
- outer.height = outer.height.saturating_add(1);
+ fn border_offsets(&self) -> BorderOffsets {
+ fn border_val(has_val: bool) -> u16 {
+ if has_val {
+ 1
+ } else {
+ 0
+ }
}
- outer
+ BorderOffsets {
+ left: border_val(self.borders.intersects(Borders::LEFT)),
+ right: border_val(self.borders.intersects(Borders::RIGHT)),
+ top: border_val(
+ self.borders.intersects(Borders::TOP)
+ || self.left_text.is_some()
+ || self.right_text.is_some(),
+ ),
+ bottom: border_val(self.borders.intersects(Borders::BOTTOM)),
+ }
}
}
@@ -108,17 +96,17 @@ where
B: Backend,
{
let rect = draw_ctx.global_rect();
-
- frame.render_widget(
- tui::widgets::Block::default()
- .borders(self.borders)
- .border_style(self.style_sheet.border),
- rect,
- );
-
- if let Some(child) = &mut self.child {
- if let Some(child_draw_ctx) = draw_ctx.children().next() {
- child.draw(state_ctx, &child_draw_ctx, frame)
+ if rect.area() > 0 {
+ frame.render_widget(
+ tui::widgets::Block::default()
+ .borders(self.borders)
+ .border_style(self.style_sheet.border),
+ rect,
+ );
+ if let Some(child) = &mut self.child {
+ if let Some(child_draw_ctx) = draw_ctx.children().next() {
+ child.draw(state_ctx, &child_draw_ctx, frame)
+ }
}
}
}
@@ -136,29 +124,45 @@ where
Status::Ignored
}
- fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> crate::tuine::Size {
+ fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size {
if let Some(child) = &self.child {
- // Reduce bounds based on borders
- let inner_rect = self.inner_rect(Rect::new(0, 0, bounds.max_width, bounds.max_height));
- let child_bounds = Bounds {
- min_width: bounds.min_width,
- min_height: bounds.min_height,
- max_width: inner_rect.width,
- max_height: inner_rect.height,
- };
-
- let mut child_node = LayoutNode::default();
- let child_size = child.layout(child_bounds, &mut child_node);
-
- child_node.rect = Rect::new(
- inner_rect.x,
- inner_rect.y,
- child_size.width,
- child_size.height,
- );
- node.children = vec![child_node];
+ let BorderOffsets {
+ left: left_offset,
+ right: right_offset,
+ top: top_offset,
+ bottom: bottom_offset,
+ } = self.border_offsets();
+
+ let vertical_offset = top_offset + bottom_offset;
+ let horizontal_offset = left_offset + right_offset;
+
+ if bounds.max_height > vertical_offset && bounds.max_width > horizontal_offset {
+ let max_width = bounds.max_width - horizontal_offset;
+ let max_height = bounds.max_height - vertical_offset;
+
+ let child_bounds = Bounds {
+ min_width: bounds.min_width,
+ min_height: bounds.min_height,
+ max_width,
+ max_height,
+ };
+ let mut child_node = LayoutNode::default();
+ let child_size = child.layout(child_bounds, &mut child_node);
- self.outer_size(child_size)
+ child_node.rect =
+ Rect::new(left_offset, top_offset, child_size.width, child_size.height);
+ node.children = vec![child_node];
+
+ Size {
+ width: child_size.width + horizontal_offset,
+ height: child_size.height + vertical_offset,
+ }
+ } else {
+ Size {
+ width: 0,
+ height: 0,
+ }
+ }
} else {
Size {
width: 0,
@@ -167,3 +171,278 @@ where
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use crate::tuine::Empty;
+
+ use super::*;
+
+ fn assert_border_offset(block: Block<(), Empty>, left: u16, right: u16, top: u16, bottom: u16) {
+ let offsets = block.border_offsets();
+ assert_eq!(offsets.left, left, "left offset should be equal");
+ assert_eq!(offsets.right, right, "right offset should be equal");
+ assert_eq!(offsets.top, top, "top offset should be equal");
+ assert_eq!(offsets.bottom, bottom, "bottom offset should be equal");
+ }
+
+ #[test]
+ fn empty_border_offset() {
+ let block: Block<(), Empty> = Block::with_child(Empty::default()).borders(Borders::empty());
+ assert_border_offset(block, 0, 0, 0, 0);
+ }
+
+ #[test]
+ fn all_border_offset() {
+ let block: Block<(), Empty> = Block::with_child(Empty::default());
+ assert_border_offset(block, 1, 1, 1, 1);
+ }
+
+ #[test]
+ fn horizontal_border_offset() {
+ let block: Block<(), Empty> =
+ Block::with_child(Empty::default()).borders(Borders::LEFT.union(Borders::RIGHT));
+ assert_border_offset(block, 1, 1, 0, 0);
+ }
+
+ #[test]
+ fn vertical_border_offset() {
+ let block: Block<(), Empty> =
+ Block::with_child(Empty::default()).borders(Borders::BOTTOM.union(Borders::TOP));
+ assert_border_offset(block, 0, 0, 1, 1);
+ }
+
+ #[test]
+ fn top_right() {
+ let block: Block<(), Empty> =
+ Block::with_child(Empty::default()).borders(Borders::RIGHT.union(Borders::TOP));
+ assert_border_offset(block, 0, 1, 1, 0);
+ }
+
+ #[test]
+ fn bottom_left() {
+ let block: Block<(), Empty> =
+ Block::with_child(Empty::default()).borders(Borders::BOTTOM.union(Borders::LEFT));
+ assert_border_offset(block, 1, 0, 0, 1);
+ }
+
+ #[test]
+ fn full_layout() {
+ let block: Block<(), Empty> = Block::with_child(Empty::default());
+ let mut layout_node = LayoutNode::default();
+ let bounds = Bounds {
+ min_width: 0,
+ min_height: 0,
+ max_width: 10,
+ max_height: 10,
+ };
+
+ assert_eq!(
+ block.layout(bounds, &mut layout_node),
+ Size {
+ width: 10,
+ height: 10,
+ },
+ "the block should have dimensions (10, 10)."
+ );
+
+ assert_eq!(
+ layout_node.children[0].rect,
+ Rect {
+ x: 1,
+ y: 1,
+ width: 8,
+ height: 8
+ },
+ "the only child should have an offset of (1, 1), and dimensions (8, 8)"
+ );
+ }
+
+ #[test]
+ fn vertical_layout() {
+ let block: Block<(), Empty> =
+ Block::with_child(Empty::default()).borders(Borders::BOTTOM.union(Borders::TOP));
+ let mut layout_node = LayoutNode::default();
+ let bounds = Bounds {
+ min_width: 0,
+ min_height: 0,
+ max_width: 10,
+ max_height: 10,
+ };
+
+ assert_eq!(
+ block.layout(bounds, &mut layout_node),
+ Size {
+ width: 10,
+ height: 10,
+ },
+ "the block should have dimensions (10, 10)."
+ );
+
+ assert_eq!(
+ layout_node.children[0].rect,
+ Rect {
+ x: 0,
+ y: 1,
+ width: 10,
+ height: 8
+ },
+ "the only child should have an offset of (0, 1), and dimensions (10, 8)"
+ );
+ }
+
+ #[test]
+ fn horizontal_layout() {
+ let block: Block<(), Empty> =
+ Block::with_child(Empty::default()).borders(Borders::LEFT.union(Borders::RIGHT));
+ let mut layout_node = LayoutNode::default();
+ let bounds = Bounds {
+ min_width: 0,
+ min_height: 0,
+ max_width: 10,
+ max_height: 10,
+ };
+
+ assert_eq!(
+ block.layout(bounds, &mut layout_node),
+ Size {
+ width: 10,
+ height: 10,
+ },
+ "the block should have dimensions (10, 10)."
+ );
+
+ assert_eq!(
+ layout_node.children[0].rect,
+ Rect {
+ x: 1,
+ y: 0,
+ width: 8,
+ height: 10
+ },
+ "the only child should have an offset of (1, 0), and dimensions (8, 10)"
+ );
+ }
+
+ #[test]
+ fn irregular_layout_one() {
+ let block: Block<(), Empty> =
+ Block::with_child(Empty::default()).borders(Borders::LEFT.union(Borders::TOP));
+ let mut layout_node = LayoutNode::default();
+ let bounds = Bounds {
+ min_width: 0,
+ min_height: 0,
+ max_width: 10,
+ max_height: 10,
+ };
+
+ assert_eq!(
+ block.layout(bounds, &mut layout_node),
+ Size {
+ width: 10,
+ height: 10,
+ },
+ "the block should have dimensions (10, 10)."
+ );
+
+ assert_eq!(
+ layout_node.children[0].rect,
+ Rect {
+ x: 1,
+ y: 1,
+ width: 9,
+ height: 9
+ },
+ "the only child should have an offset of (1, 1), and dimensions (9, 9)"
+ );
+ }
+
+ #[test]
+ fn irregular_layout_two() {
+ let block: Block<(), Empty> =
+ Block::with_child(Empty::default()).borders(Borders::BOTTOM.union(Borders::RIGHT));
+ let mut layout_node = LayoutNode::default();
+ let bounds = Bounds {
+ min_width: 0,
+ min_height: 0,
+ max_width: 10,
+ max_height: 10,
+ };
+
+ assert_eq!(
+ block.layout(bounds, &mut layout_node),
+ Size {
+ width: 10,
+ height: 10,
+ },
+ "the block should have dimensions (10, 10)."
+ );
+
+ assert_eq!(
+ layout_node.children[0].rect,
+ Rect {
+ x: 0,
+ y: 0,
+ width: 9,
+ height: 9
+ },
+ "the only child should have an offset of (0, 0), and dimensions (9, 9)"
+ );
+ }
+
+ #[test]
+ fn irregular_layout_three() {
+ let block: Block<(), Empty> =
+ Block::with_child(Empty::default()).borders(Borders::RIGHT.union(Borders::TOP));
+ let mut layout_node = LayoutNode::default();
+ let bounds = Bounds {
+ min_width: 0,
+ min_height: 0,
+ max_width: 10,
+ max_height: 10,
+ };
+
+ assert_eq!(
+ block.layout(bounds, &mut layout_node),
+ Size {
+ width: 10,
+ height: 10,
+ },
+ "the block should have dimensions (10, 10)."
+ );
+
+ assert_eq!(
+ layout_node.children[0].rect,
+ Rect {
+ x: 0,
+ y: 1,
+ width: 9,
+ height: 9
+ },
+ "the only child should have an offset of (0, 1), and dimensions (9, 9)"
+ );
+ }
+
+ #[test]
+ fn too_small_layout() {
+ let block: Block<(), Empty> = Block::with_child(Empty::default());
+ let mut layout_node = LayoutNode::default();
+ let bounds = Bounds {
+ min_width: 0,
+ min_height: 0,
+ max_width: 2,
+ max_height: 2,
+ };
+
+ assert_eq!(
+ block.layout(bounds, &mut layout_node),
+ Size {
+ width: 0,
+ height: 0,
+ },
+ "the area should be 0"
+ );
+
+ assert_eq!(layout_node.children.len(), 0, "layout node should be empty");
+ }
+}
diff --git a/src/tuine/component/base/flex/mod.rs b/src/tuine/component/base/flex/mod.rs
index d4262069..99a8f555 100644
--- a/src/tuine/component/base/flex/mod.rs
+++ b/src/tuine/component/base/flex/mod.rs
@@ -191,14 +191,16 @@ impl<Message> TmpComponent<Message> for Flex<Message> {
// If there is still remaining space after, distribute the rest if
// appropriate (e.x. current_size is too small for the bounds).
if current_size.width < bounds.min_width {
- // For now, we'll cheat and just set it to be equal.
+ // FIXME: For now, we'll cheat and just set it to be equal.
current_size.width = bounds.min_width;
}
if current_size.height < bounds.min_height {
- // For now, we'll cheat and just set it to be equal.
+ // FIXME: For now, we'll cheat and just set it to be equal.
current_size.height = bounds.min_height;
}
+ // FIXME: Remove area 0 children
+
// Now that we're done determining sizes, convert all children into the appropriate
// layout nodes. Remember - parents determine children, and so, we determine
// children here!
diff --git a/src/tuine/component/base/padding.rs b/src/tuine/component/base/padding.rs
index e69de29b..c7c1ef9e 100644
--- a/src/tuine/component/base/padding.rs
+++ b/src/tuine/component/base/padding.rs
@@ -0,0 +1,16 @@
+use std::marker::PhantomData;
+
+use crate::tuine::TmpComponent;
+
+/// A [`Padding`] surrounds a child widget with spacing.
+pub struct Padding<Child, Message>
+where
+ Child: TmpComponent<Message>,
+{
+ _pd: PhantomData<Message>,
+ padding_left: u16,
+ padding_right: u16,
+ padding_up: u16,
+ padding_down: u16,
+ child: Option<Child>,
+}
diff --git a/src/tuine/component/base/shortcut.rs b/src/tuine/component/base/shortcut.rs
index 812a3d63..7300341f 100644
--- a/src/tuine/component/base/shortcut.rs
+++ b/src/tuine/component/base/shortcut.rs
@@ -8,8 +8,8 @@ use rustc_hash::FxHashMap;
use tui::{backend::Backend, layout::Rect, Frame};
use crate::tuine::{
- Bounds, DrawContext, Event, Key, LayoutNode, Size, StateContext, StatefulComponent, Status,
- TmpComponent,
+ Bounds, BuildContext, DrawContext, Event, Key, LayoutNode, Size, StateContext,
+ StatefulComponent, Status, TmpComponent,
};
const MAX_TIMEOUT: Duration = Duration::from_millis(400);
@@ -145,7 +145,7 @@ where
type ComponentState = ShortcutState;
- fn build(ctx: &mut crate::tuine::BuildContext<'_>, props: Self::Properties) -> Self {
+ fn build(ctx: &mut BuildContext<'_>, props: Self::Properties) -> Self {
let (key, state) =
ctx.register_and_mut_state::<_, Self::ComponentState>(Location::caller());
let mut forest: FxHashMap<Vec<Event>, bool> = FxHashMap::default();
diff --git a/src/tuine/component/base/text_table/mod.rs b/src/tuine/component/base/text_table/mod.rs
index b29338e8..6b08c8d1 100644
--- a/src/tuine/component/base/text_table/mod.rs
+++ b/src/tuine/component/base/text_table/mod.rs
@@ -90,7 +90,7 @@ impl<Message> TextTable<Message> {
0
} else {
// +1 for the spacing
- width_remaining -= width + 1;
+ width_remaining = width_remaining.saturating_sub(width + 1);
width
}
})
@@ -372,7 +372,7 @@ impl<Message> TmpComponent<Message> for TextTable<Message> {
#[cfg(test)]
mod tests {
use crate::tuine::{
- text_table::SortType, StateMap, StatefulComponent, TextTableProps, BuildContext,
+ text_table::SortType, BuildContext, StateMap, StatefulComponent, TextTableProps,
};
use super::{DataRow, TextTable};
diff --git a/src/tuine/component/base/time_graph.rs b/src/tuine/component/base/time_graph.rs
index 6059dce7..9c899243 100644
--- a/src/tuine/component/base/time_graph.rs
+++ b/src/tuine/component/base/time_graph.rs
@@ -27,28 +27,3 @@ pub struct TimeGraph {
}
impl TimeGraph {}
-
-impl<Message> TmpComponent<Message> for TimeGraph {
- fn draw<Backend>(
- &mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
- frame: &mut Frame<'_, Backend>,
- ) where
- Backend: tui::backend::Backend,
- {
- todo!()
- }
-
- fn on_event(
- &mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>, event: Event,
- messages: &mut Vec<Message>,
- ) -> Status {
- Status::Ignored
- }
-
- fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> crate::tuine::Size {
- crate::tuine::Size {
- width: bounds.max_width,
- height: bounds.max_height,
- }
- }
-}
diff --git a/src/tuine/component/mod.rs b/src/tuine/component/mod.rs
index aaea0f32..b7a2224e 100644
--- a/src/tuine/component/mod.rs
+++ b/src/tuine/component/mod.rs
@@ -10,8 +10,7 @@ pub use stateful::*;
pub mod banner;
pub use banner::*;
-// pub mod stateless;
-// pub use stateless::*;
+
use enum_dispatch::enum_dispatch;
use tui::Frame;
diff --git a/src/tuine/context/context.rs b/src/tuine/context/context.rs
new file mode 100644
index 00000000..ea06ce18
--- /dev/null
+++ b/src/tuine/context/context.rs
@@ -0,0 +1,35 @@
+use gapbuffer::GapBuffer;
+
+use crate::tuine::{Key, KeyCreator, State};
+
+/// A [`Context`] is used to create a [`Component`](super::Component).
+///
+/// The internal implementation is based on Jetpack Compose's [Positional Memoization](https://medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd),
+/// in addition to [Crochet](https://github.com/raphlinus/crochet/blob/master/src/tree.rs) in its entirety.
+pub struct Context {
+ component_key_creator: KeyCreator,
+ buffer: GapBuffer<Slot>,
+}
+
+enum Payload {
+ State(Box<dyn State>),
+ View,
+}
+
+struct Item {
+ key: Key,
+ payload: Payload,
+}
+
+enum Slot {
+ Begin(Item),
+ End,
+}
+
+impl Context {
+ pub fn use_state(&self) {}
+
+ pub fn start(&mut self) {}
+
+ pub fn end(&mut self) {}
+}
diff --git a/src/tuine/context/mod.rs b/src/tuine/context/mod.rs
index de4ee08e..904a52df 100644
--- a/src/tuine/context/mod.rs
+++ b/src/tuine/context/mod.rs
@@ -9,3 +9,6 @@ pub use build_context::BuildContext;
pub mod state_context;
pub use state_context::StateContext;
+
+pub mod context;
+pub use context::Context;
diff --git a/src/tuine/key.rs b/src/tuine/key.rs
index 05e70152..16f65980 100644
--- a/src/tuine/key.rs
+++ b/src/tuine/key.rs
@@ -30,3 +30,19 @@ impl Key {
}
}
}
+
+#[derive(Default, Clone, Copy, Debug)]
+pub struct KeyCreator {
+ index: usize,
+}
+
+impl KeyCreator {
+ pub fn new_key(&mut self, caller: impl Into<Caller>) -> Key {
+ self.index += 1;
+ Key::new(caller, self.index)
+ }
+
+ pub fn reset(&mut self) {
+ self.index = 0;
+ }
+}
diff --git a/src/tuine/layout/size.rs b/src/tuine/layout/size.rs
index 74bbf4df..003ae8fe 100644
--- a/src/tuine/layout/size.rs
+++ b/src/tuine/layout/size.rs
@@ -4,7 +4,7 @@ use std::ops::{Add, AddAssign};
///
/// A [`Size`] is sent from a child component back up to its parents after
/// first being given a [`Bounds`](super::Bounds) from the parent.
-#[derive(Clone, Copy, Debug, Default)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Size {
/// The width that the component has determined.
pub width: u16,
diff --git a/src/tuine/mod.rs b/src/tuine/mod.rs
index f7ec3ede..111de69d 100644
--- a/src/tuine/mod.rs
+++ b/src/tuine/mod.rs
@@ -1,14 +1,22 @@
//! tuine is a wrapper around tui-rs that expands on it with state management and
//! event handling.
//!
-//! tuine is inspired by a **ton** of other libraries and frameworks, like:
+//! tuine is inspired by a **ton** of other libraries and frameworks:
//!
//! - [Crochet](https://github.com/raphlinus/crochet)
+//! - [Dioxus](https://github.com/DioxusLabs/dioxus)
//! - [Druid](https://github.com/linebender/druid)
//! - [Flutter](https://flutter.dev/)
//! - [Iced](https://github.com/iced-rs/iced)
+//! - [Jetpack Compose](https://developer.android.com/jetpack/compose)
//! - [React](https://reactjs.org/)
//! - [Yew](https://yew.rs/)
+//!
+//! In addition, Raph Levien's post,
+//! [*Towards principled reactive UI](https://raphlinus.github.io/rust/druid/2020/09/25/principled-reactive-ui.html),
+//! was a fantastic source of information for someone like me who had basically zero knowledge heading in.
+//!
+//! None of this would be possible without these as reference points and sources of inspiration and learning!
mod tui_rs;
diff --git a/tests/invalid_config_tests.rs b/tests/invalid_config_tests.rs
index f8908b5c..5ce0f9b5 100644
--- a/tests/invalid_config_tests.rs
+++ b/tests/invalid_config_tests.rs
@@ -25,7 +25,7 @@ fn test_empty_layout() {
.arg("./tests/invalid_configs/empty_layout.toml")
.assert()
.failure()
- .stderr(predicate::str::contains("Configuration file error")); // FIXME: [Urgent] Use a const for the error pattern
+ .stderr(predicate::str::contains("cannot be empty")); // FIXME: [Urgent] Use a const for the error pattern
}
#[test]