Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a ScrollView and refactor painting logic with a newly added Canvas #25

Merged
merged 2 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
geometry::{Point, Size},
view::{Cx, View},
widget::{
BoxConstraints, CxState, Event, EventCx, LayoutCx, LifeCycle, LifeCycleCx, Message,
BoxConstraints, Canvas, CxState, Event, EventCx, LayoutCx, LifeCycle, LifeCycleCx, Message,
PaintCx, Pod, PodFlags, ViewContext, WidgetState,
},
};
Expand Down Expand Up @@ -342,7 +342,7 @@ impl<T: Send + 'static, V: View<T> + 'static> App<T, V> {
let mut paint_cx = PaintCx {
widget_state: &mut self.root_state,
cx_state,
terminal: &mut self.terminal,
canvas: &mut Canvas::new(self.terminal.current_buffer_mut()),
override_style: ratatui::style::Style::default(),
};

Expand Down
9 changes: 0 additions & 9 deletions src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,6 @@ pub enum Axis {
Vertical,
}

pub fn to_ratatui_rect(rect: Rect) -> ratatui::layout::Rect {
ratatui::layout::Rect {
x: rect.x0.round().clamp(0.0, u16::MAX as f64) as u16,
y: rect.y0.round().clamp(0.0, u16::MAX as f64) as u16,
width: rect.x1.round().clamp(0.0, u16::MAX as f64) as u16,
height: rect.y1.round().clamp(0.0, u16::MAX as f64) as u16,
}
}

impl Axis {
/// Returns the orthogonal axis.
///
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod widget;
pub use app::App;
pub use ratatui::style::{Color, Modifier, Style};
pub use view::*;
pub use widget::{CatchMouseButton, ChangeFlags};
pub use widget::{Canvas, CatchMouseButton, ChangeFlags};

#[cfg(test)]
mod test_helper;
4 changes: 1 addition & 3 deletions src/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,7 @@ impl Widget for DebugWidget {
fn paint(&mut self, cx: &mut crate::widget::PaintCx) {
self.content.paint(cx);

cx.terminal.flush().unwrap();

let buffer = cx.terminal.backend().buffer().to_owned();
let buffer = cx.canvas.buffer.to_owned();
self.debug_chan_tx.blocking_send(buffer).unwrap();
}

Expand Down
2 changes: 2 additions & 0 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod events;
mod fill_max_size;
mod linear_layout;
mod margin;
mod scroll_view;
mod text;
mod use_state;
mod weighted_linear_layout;
Expand All @@ -26,6 +27,7 @@ pub use events::*;
pub use fill_max_size::*;
pub use linear_layout::*;
pub use margin::*;
pub use scroll_view::*;
pub use text::*;
pub use use_state::*;
pub use weighted_linear_layout::*;
Expand Down
64 changes: 64 additions & 0 deletions src/view/scroll_view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::{any::Any, marker::PhantomData};

use crate::{view::View, widget::ChangeFlags};

use xilem_core::{Id, MessageResult};

use super::{Cx, ViewMarker, ViewSequence};

pub struct ScrollView<T, A, C> {
child: C,
phantom: PhantomData<fn() -> (T, A)>,
}

pub fn scroll_view<T, A, C>(child: C) -> ScrollView<T, A, C> {
ScrollView::new(child)
}

impl<T, A, C> ScrollView<T, A, C> {
pub fn new(child: C) -> Self {
ScrollView {
child,
phantom: Default::default(),
}
}
}

impl<T, A, VT: ViewSequence<T, A>> ViewMarker for ScrollView<T, A, VT> {}

impl<T, A, C: View<T, A>> View<T, A> for ScrollView<T, A, C>
where
C::Element: 'static,
{
type State = C::State;

type Element = crate::widget::ScrollView;

fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
let (id, child_state, child_element) = self.child.build(cx);
let element = crate::widget::ScrollView::new(child_element);
(id, child_state, element)
}

fn rebuild(
&self,
cx: &mut Cx,
prev: &Self,
id: &mut Id,
state: &mut Self::State,
element: &mut Self::Element,
) -> ChangeFlags {
let child_el = element.child_mut().downcast_mut().unwrap();
self.child.rebuild(cx, &prev.child, id, state, child_el)
}

fn message(
&self,
id_path: &[Id],
state: &mut Self::State,
message: Box<dyn Any>,
app_state: &mut T,
) -> MessageResult<A> {
self.child.message(id_path, state, message, app_state)
}
}
5 changes: 4 additions & 1 deletion src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ mod events;
mod fill_max_size;
mod linear_layout;
mod margin;
mod scroll_view;
mod text;
mod weighted_linear_layout;

pub use self::core::{
AnyWidget, ChangeFlags, CxState, EventCx, LayoutCx, LifeCycleCx, Message, PaintCx, Pod, Widget,
AnyWidget, Canvas, ChangeFlags, CxState, EventCx, LayoutCx, LifeCycleCx, Message, PaintCx, Pod,
Widget,
};
pub(crate) use self::core::{PodFlags, WidgetState};
pub(crate) use border::Border;
Expand All @@ -25,5 +27,6 @@ pub use events::*;
pub(crate) use fill_max_size::FillMaxSize;
pub(crate) use linear_layout::LinearLayout;
pub(crate) use margin::Margin;
pub(crate) use scroll_view::ScrollView;
pub(crate) use text::*;
pub(crate) use weighted_linear_layout::{WeightedLayoutElement, WeightedLinearLayout};
56 changes: 31 additions & 25 deletions src/widget/border.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{
core::LayoutCx, core::PaintCx, BoxConstraints, ChangeFlags, Event, EventCx, Pod, Widget,
};
use crate::{
geometry::{to_ratatui_rect, Point, Size},
geometry::{Point, Size},
view::Borders,
BorderKind,
};
Expand Down Expand Up @@ -60,90 +60,96 @@ impl Border {
}

fn render_border(&self, cx: &mut PaintCx) {
use Borders as B; // unfortunately not possible to wildcard import since it's not an enum...

let style = self.style.patch(cx.override_style);
cx.override_style = Style::default();
let r = to_ratatui_rect(cx.rect());
let s = cx.size();
let width = s.width.round() as usize;
let height = s.height.round() as usize;

use Borders as B; // unfortunately not possible to wildcard import since it's not an enum...
if r.width == 0 || r.height == 0 {
if width == 0 || height == 0 {
return;
}

let buf = cx.terminal.current_buffer_mut();
let canvas = &mut cx.canvas;

let mut draw = |x, y, symbol, style| {
if buf.area.x + x < buf.area.width && buf.area.y + y < buf.area.height {
buf.get_mut(x, y).set_symbol(symbol).set_style(style);
if x < width && y < height {
canvas
.get_mut((x as f64, y as f64))
.set_symbol(symbol)
.set_style(style);
}
};

// Voluntary extra task, find cases where a dot makes sense as well (like `TOP | LEFT`)...
if r.width == 1 && r.height == 1 && self.borders.intersects(B::ALL_CORNERS) {
draw(r.x, r.y, symbols::DOT, self.style);
if s.width == 1.0 && s.height == 1.0 && self.borders.intersects(B::ALL_CORNERS) {
draw(0, 0, symbols::DOT, self.style);
return;
}

// borders
if self.borders.intersects(B::HORIZONTAL) {
let start = if self.borders.intersects(B::LEFT_WITH_CORNERS) {
r.x + 1
1
} else {
r.x
0
};
let end = if self.borders.intersects(B::RIGHT_WITH_CORNERS) {
r.x + r.width - 1
width - 1
} else {
r.x + r.width
width
};
if self.borders.contains(B::TOP) {
for x in start..end {
draw(x, r.y, self.kind.symbols().horizontal, style);
draw(x, 0, self.kind.symbols().horizontal, style);
}
}
if self.borders.contains(B::BOTTOM) {
for x in start..end {
draw(x, r.y + r.height - 1, self.kind.symbols().horizontal, style);
draw(x, height - 1, self.kind.symbols().horizontal, style);
}
}
}
if self.borders.intersects(B::VERTICAL) {
let start = if self.borders.intersects(B::TOP_WITH_CORNERS) {
r.y + 1
1
} else {
r.y
0
};
let end = if self.borders.intersects(B::BOTTOM_WITH_CORNERS) {
r.y + r.height - 1
height - 1
} else {
r.y + r.height
height
};
if self.borders.contains(B::LEFT) {
for y in start..end {
draw(r.x, y, self.kind.symbols().vertical, style);
draw(0, y, self.kind.symbols().vertical, style);
}
}
if self.borders.contains(B::RIGHT) {
for y in start..end {
draw(r.x + r.width - 1, y, self.kind.symbols().vertical, style);
draw(width - 1, y, self.kind.symbols().vertical, style);
}
}
}

// corners
if self.borders.contains(B::TOP_LEFT_CORNER) {
draw(r.x, r.y, self.kind.symbols().top_left, style);
draw(0, 0, self.kind.symbols().top_left, style);
}
if self.borders.contains(B::BOTTOM_LEFT_CORNER) {
let symbol = self.kind.symbols().bottom_left;
draw(r.x, r.y + r.height - 1, symbol, style);
draw(0, height - 1, symbol, style);
}
if self.borders.contains(B::BOTTOM_RIGHT_CORNER) {
let symbol = self.kind.symbols().bottom_right;
draw(r.x + r.width - 1, r.y + r.height - 1, symbol, style);
draw(width - 1, height - 1, symbol, style);
}
if self.borders.contains(B::TOP_RIGHT_CORNER) {
let symbol = self.kind.symbols().top_right;
draw(r.x + r.width - 1, r.y, symbol, style);
draw(width - 1, 0, symbol, style);
}
}
}
Expand Down
Loading