Merge pull request #36 from osui-rs/refactor

Refactor
This commit is contained in:
Klesti Selimaj
2025-11-02 12:31:59 +01:00
committed by GitHub
9 changed files with 318 additions and 140 deletions
+2 -2
View File
@@ -64,7 +64,7 @@ pub fn app(screen: Arc<Screen>) -> Rsx {
FlexRow
{
"Username"
@Transform::new().padding(1, 1).dimensions(40, 1);
@Transform::new().padding(1, 1).dimensions_const(40, 1);
@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xff0000) };
@Focused;
Input { }
@@ -73,7 +73,7 @@ pub fn app(screen: Arc<Screen>) -> Rsx {
FlexRow
{
"Password"
@Transform::new().padding(1, 1).dimensions(40, 1);
@Transform::new().padding(1, 1).dimensions_const(40, 1);
@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xffff00) };
Input { }
}
+52
View File
@@ -0,0 +1,52 @@
use std::sync::Arc;
use osui::prelude::*;
fn main() -> std::io::Result<()> {
let screen = Screen::new();
screen.extension(InputExtension);
app(screen.clone()).draw(&screen);
screen.run()
}
pub fn app(screen: Arc<Screen>) -> Rsx {
rsx! {
@Handler::new({
let screen = screen.clone();
move |_, e: &crossterm::event::Event| {
if let crossterm::event::Event::Key(e) = e {
if e.code == crossterm::event::KeyCode::Esc {
screen.close();
}
}
}
});
@Style { foreground: None, background: Background::Outline(0xff0000) };
VelocityHandler {
@Velocity(700, 0);
@Handler::new({
move |w, e: &crossterm::event::Event| {
if let crossterm::event::Event::Key(e) = e {
if e.code == crossterm::event::KeyCode::Char(' ') {
if let Some(Velocity(v, _)) = w.get() {
w.set_component(Velocity(-v, 0));
} else {
w.component(Velocity(700, 0));
}
}
}
}
});
"(•_•)"
}
@Transform::new().dimensions(Dimension::Full, Dimension::Full);
VelocityHandler {
@Velocity(0, -150);
@Transform::center().bottom();
"^"
}
}
}
+2
View File
@@ -4,6 +4,7 @@ pub mod heading;
pub mod input;
pub mod paginator;
pub mod scroll;
pub mod velocity_handler;
pub use div::*;
pub use flex::*;
@@ -11,6 +12,7 @@ pub use heading::*;
pub use input::*;
pub use paginator::*;
pub use scroll::*;
pub use velocity_handler::*;
use crate::Element;
+164
View File
@@ -0,0 +1,164 @@
use std::sync::Arc;
use crate::{
component,
prelude::ElementRenderer,
style::RawTransform,
widget::{Element, Widget},
NoRenderRoot,
};
pub struct VelocityRenderer<'a>(&'a mut RawTransform, &'a mut Vec<(i32, i32)>, usize, u16);
pub struct VelocityHandler {
children: Vec<Arc<Widget>>,
progress: Vec<(i32, i32)>,
render: (u16, u16),
tick: u16,
}
impl VelocityHandler {
pub fn new() -> Self {
Self {
children: Vec::new(),
progress: Vec::new(),
render: (0, 0),
tick: 0,
}
}
}
impl Element for VelocityHandler {
fn render(
&mut self,
scope: &mut crate::prelude::RenderScope,
_: &crate::render_scope::RenderContext,
) {
scope.use_area(self.render.0, self.render.1);
}
fn after_render(
&mut self,
parent_scope: &mut crate::render_scope::RenderScope,
render_context: &crate::render_scope::RenderContext,
) {
let mut transform = {
let (width, height) = parent_scope.get_size_or_parent();
let mut t = parent_scope.get_transform().clone();
t.width = width;
t.height = height;
t.transform_parent(parent_scope.get_parent_transform());
t
};
let mut scope = crate::render_scope::RenderScope::new();
scope.set_parent_transform(transform.clone());
transform.width = 0;
transform.height = 0;
let mut renderer = VelocityRenderer(&mut transform, &mut self.progress, 0, self.tick);
for widget in &self.children {
scope.render_widget(&mut renderer, render_context.get_context(), widget);
}
self.render = (transform.width, transform.height);
if self.tick == 1000 {
self.tick = 0;
} else {
self.tick += 1;
}
}
fn draw_child(&mut self, element: &Arc<Widget>) {
self.progress.push((0, 0));
self.children.push(element.clone());
element.inject(|w| w.component(NoRenderRoot));
}
fn undraw_child(&mut self, element: &Arc<Widget>) {
if let Some(i) = self.children.iter().position(|v| Arc::ptr_eq(v, element)) {
self.children.remove(i);
self.progress.remove(i);
}
}
fn is_ghost(&mut self) -> bool {
true
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl ElementRenderer for VelocityRenderer<'_> {
fn before_draw(&mut self, scope: &mut crate::prelude::RenderScope, widget: &Arc<Widget>) {
let t = scope.get_transform_mut();
if let Some(Velocity(vx, vy)) = widget.get() {
if let Some((ax, ay)) = self.1.get_mut(self.2) {
add_i32_to_u16(&mut t.x, *ax);
add_i32_to_u16(&mut t.y, *ay);
if apply_velocity(self.3, vx, &mut t.x) {
apply_value(ax, vx);
}
if apply_velocity(self.3, vy, &mut t.y) {
apply_value(ay, vy);
}
} else {
self.1.push((0, 0));
}
}
self.2 += 1;
self.0.width = self.0.width.max(t.x + t.width + (t.px * 2));
self.0.height = self.0.height.max(t.y + t.height + (t.py * 2));
t.x += self.0.x + self.0.px;
t.y += self.0.y + self.0.py;
t.offset_y = self.0.offset_y;
}
}
fn apply_velocity(ticks: u16, velocity: i32, x: &mut u16) -> bool {
let abs_v = velocity.abs();
if abs_v == 0 {
return false;
}
let interval = (1000 / abs_v).max(1); // prevent divide-by-zero
if ticks as i32 % interval == 0 {
if velocity > 0 {
*x = x.saturating_add(1);
} else {
*x = x.saturating_sub(1);
}
return true;
}
false
}
fn add_i32_to_u16(value: &mut u16, delta: i32) {
if delta >= 0 {
*value = value.saturating_add(delta as u16);
} else {
*value = value.saturating_sub((-delta) as u16);
}
}
fn apply_value(x: &mut i32, velocity: i32) {
*x = if velocity > 0 {
x.saturating_add(1)
} else {
x.saturating_sub(1)
};
}
component!(Velocity(pub i32, pub i32));
-2
View File
@@ -2,13 +2,11 @@ pub mod focus;
pub mod id;
pub mod input_handling;
pub mod tick;
pub mod velocity;
pub use focus::*;
pub use id::*;
pub use input_handling::*;
pub use tick::*;
pub use velocity::*;
use std::{
any::{type_name, Any},
-64
View File
@@ -1,64 +0,0 @@
use std::sync::Arc;
use crate::{
component,
extensions::Extension,
style::{Position, Transform},
widget::Widget,
};
pub struct VelocityExtension;
component!(Velocity(pub i32, pub i32));
impl VelocityExtension {
fn apply_velocity(ticks: u16, velocity: i32, x: &mut u16) {
if velocity.abs() != 0 && ticks as i32 % (1000 / velocity.abs()) == 0 {
if velocity > 0 {
*x += 1;
} else if velocity < 0 {
*x -= 1;
}
}
}
fn apply_velocity_xy(ticks: u16, widget: &Arc<Widget>) {
if let Some(velocity) = widget.get::<Velocity>() {
if let Some(mut t) = widget.get::<Transform>() {
match &mut t.x {
Position::Const(x) => Self::apply_velocity(ticks, velocity.0, x),
_ => {}
}
match &mut t.y {
Position::Const(y) => Self::apply_velocity(ticks, velocity.1, y),
_ => {}
}
widget.set_component(t);
}
}
}
}
impl Extension for VelocityExtension {
fn init(&mut self, ctx: &super::Context) {
let ctx = ctx.clone();
std::thread::spawn({
move || {
let mut tick = 0;
loop {
for widget in ctx.get_widgets().iter() {
Self::apply_velocity_xy(tick, widget);
}
if tick > 1000 {
tick = 0;
} else {
tick += 1;
}
std::thread::sleep(std::time::Duration::from_millis(1));
}
}
});
}
}
+46 -63
View File
@@ -84,23 +84,10 @@ impl RenderScope {
}
}
/// Directly sets the raw transform (position and size) for this scope.
pub fn set_transform_raw(&mut self, transform: RawTransform) {
self.transform = transform;
}
/// Applies a `Transform` to this scope, factoring in parent dimensions.
pub fn set_transform(&mut self, transform: &Transform) {
transform.use_dimensions(
self.parent_transform.width,
self.parent_transform.height,
&mut self.transform,
);
transform.use_position(
self.parent_transform.width,
self.parent_transform.height,
&mut self.transform,
);
pub fn apply_transform(&mut self, transform: &Transform) {
transform.use_dimensions(&self.parent_transform, &mut self.transform);
transform.use_position(&self.parent_transform, &mut self.transform);
self.transform.px = transform.px;
self.transform.py = transform.py;
}
@@ -114,7 +101,7 @@ impl RenderScope {
self.transform.height = self.transform.height.max(h);
}
/// Adds a text draw instruction.
/// Adds a text draw instruction (inverted colors).
pub fn draw_text_inverted(&mut self, x: u16, y: u16, text: &str) {
self.render_stack
.push(RenderMethod::TextInverted(x, y, text.to_string()));
@@ -161,16 +148,15 @@ impl RenderScope {
let height = height + self.transform.py * 2;
if width > 1 && height > 1 {
let d = "─".repeat(width as usize - 2);
utils::print_liner(
self.transform.x,
self.transform.y,
utils::print_liner_nopad(
&self.transform,
&self.parent_transform,
&utils::hex_ansi(c),
&format!(
"┌{d}┐{}\n└{d}┘",
format!("\n│{}│", " ".repeat(width as usize - 2))
.repeat(height as usize - 2),
),
&self.parent_transform,
);
}
}
@@ -179,28 +165,26 @@ impl RenderScope {
let height = height + self.transform.py * 2;
if width > 1 && height > 1 {
let d = "─".repeat(width as usize - 2);
utils::print_liner(
self.transform.x,
self.transform.y,
utils::print_liner_nopad(
&self.transform,
&self.parent_transform,
&utils::hex_ansi(c),
&format!(
"╭{d}╮{}\n╰{d}╯",
format!("\n│{}│", " ".repeat(width as usize - 2))
.repeat(height as usize - 2),
),
&self.parent_transform,
);
}
}
crate::style::Background::Solid(c) => utils::print_liner(
self.transform.x,
self.transform.y,
crate::style::Background::Solid(c) => utils::print_liner_nopad(
&self.transform,
&self.parent_transform,
&hex_ansi_bg(c),
&std::iter::repeat(" ".repeat(width as usize))
.take(height as usize)
.collect::<Vec<_>>()
.join("\n"),
&self.parent_transform,
),
}
@@ -209,55 +193,49 @@ impl RenderScope {
RenderMethod::Text(x, y, t) => {
if let Some(c) = self.style.foreground {
utils::print_liner(
self.transform.x + self.transform.px + x,
self.transform.y + self.transform.py + y,
*x,
*y,
&self.transform,
&self.parent_transform,
&utils::hex_ansi(c),
t,
&self.parent_transform,
);
} else {
utils::print(
self.transform.x + self.transform.px,
self.transform.y + self.transform.py,
t,
&self.parent_transform,
);
utils::print(*x, *y, &self.transform, &self.parent_transform, t);
}
}
RenderMethod::TextInverted(x, y, t) => {
if let Some(c) = self.style.foreground {
utils::print_liner(
self.transform.x + self.transform.px + x,
self.transform.y + self.transform.py + y,
*x,
*y,
&self.transform,
&self.parent_transform,
&utils::hex_ansi_bg(c),
t,
&self.parent_transform,
);
} else {
utils::print(
self.transform.x + self.transform.px,
self.transform.y + self.transform.py,
t,
&self.parent_transform,
);
utils::print(*x, *y, &self.transform, &self.parent_transform, t);
}
}
RenderMethod::TextColored(x, y, t, c) => utils::print_liner(
self.transform.x + self.transform.px + x,
self.transform.y + self.transform.py + y,
*x,
*y,
&self.transform,
&self.parent_transform,
&utils::hex_ansi(*c),
t,
&self.parent_transform,
),
RenderMethod::Rectangle(x, y, width, height, color) => utils::print_liner(
self.transform.x + self.transform.px + x,
self.transform.y + self.transform.py + y,
&hex_ansi_bg(*color),
RenderMethod::Rectangle(x, y, width, height, c) => utils::print_liner(
*x,
*y,
&self.transform,
&self.parent_transform,
&utils::hex_ansi_bg(*c),
&std::iter::repeat(" ".repeat(*width as usize))
.take(*height as usize)
.collect::<Vec<_>>()
.join("\n"),
&self.parent_transform,
),
}
}
@@ -306,22 +284,27 @@ impl RenderScope {
},
)
}
/// Returns parent size.
pub fn get_parent_size(&self) -> (u16, u16) {
(self.parent_transform.width, self.parent_transform.height)
}
/// Gets the dimensions of the parent container.
pub fn get_parent_transform(&self) -> &RawTransform {
&self.parent_transform
}
/// Sets the dimensions of the parent container.
// pub fn set_parent_size(&mut self, width: u16, height: u16) {
// self.parent_transform.width = width;
// self.parent_transform.height = height;
// }
/// Sets the transform of the parent container.
pub fn set_parent_transform(&mut self, transform: RawTransform) {
self.parent_transform = transform;
}
/// Sets the transform of the parent container.
pub fn set_parent_size(&mut self, width: u16, height: u16) {
self.parent_transform.width = width;
self.parent_transform.height = height;
}
/// Returns a mutable reference to the internal raw transform.
pub fn get_transform_mut(&mut self) -> &mut RawTransform {
&mut self.transform
@@ -364,14 +347,14 @@ impl RenderScope {
self.set_style(style);
}
if let Some(t) = widget.get() {
self.set_transform(&t);
self.apply_transform(&t);
}
widget.get_elem().render(self, &render_context);
ctx.render(widget, self);
if let Some(t) = widget.get() {
self.set_transform(&t);
self.apply_transform(&t);
}
renderer.before_draw(self, widget);
+16 -7
View File
@@ -139,24 +139,33 @@ impl Transform {
}
/// Sets constant dimensions.
pub fn dimensions(mut self, width: u16, height: u16) -> Self {
pub fn dimensions_const(mut self, width: u16, height: u16) -> Self {
self.width = Dimension::Const(width);
self.height = Dimension::Const(height);
self
}
/// Sets constant dimensions.
pub fn dimensions(mut self, width: Dimension, height: Dimension) -> Self {
self.width = width;
self.height = height;
self
}
/// Resolves `Dimension` rules into absolute values for the given parent size.
pub fn use_dimensions(&self, parent_width: u16, parent_height: u16, raw: &mut RawTransform) {
self.width.use_dimension(parent_width, &mut raw.width);
self.height.use_dimension(parent_height, &mut raw.height);
pub fn use_dimensions(&self, parent_transform: &RawTransform, raw: &mut RawTransform) {
self.width
.use_dimension(parent_transform.width, &mut raw.width);
self.height
.use_dimension(parent_transform.height, &mut raw.height);
}
/// Resolves `Position` rules into absolute positions for the given parent size.
pub fn use_position(&self, parent_width: u16, parent_height: u16, raw: &mut RawTransform) {
pub fn use_position(&self, parent_transform: &RawTransform, raw: &mut RawTransform) {
self.x
.use_position(raw.width, parent_width, self.mx, &mut raw.x);
.use_position(raw.width, parent_transform.width, self.mx, &mut raw.x);
self.y
.use_position(raw.height, parent_height, self.my, &mut raw.y);
.use_position(raw.height, parent_transform.height, self.my, &mut raw.y);
}
}
+36 -2
View File
@@ -109,7 +109,16 @@ pub fn hex_ansi_bg(hex: u32) -> String {
format!("\x1b[48;2;{r};{g};{b}m")
}
pub(crate) fn print(x: u16, y: u16, text: &str, parent_transform: &RawTransform) {
pub(crate) fn print(
x: u16,
y: u16,
transform: &RawTransform,
parent_transform: &RawTransform,
text: &str,
) {
let x = transform.x + transform.px + x;
let y = transform.y + transform.py + y;
for (i, line) in text.lines().enumerate() {
if parent_transform.offset_y > y + i as u16 {
continue;
@@ -128,10 +137,14 @@ pub(crate) fn print(x: u16, y: u16, text: &str, parent_transform: &RawTransform)
pub(crate) fn print_liner(
x: u16,
y: u16,
transform: &RawTransform,
parent_transform: &RawTransform,
liner: &str,
text: &str,
parent_transform: &RawTransform,
) {
let x = transform.x + transform.px + x;
let y = transform.y + transform.py + y;
for (i, line) in text.lines().enumerate() {
if parent_transform.offset_y > y + i as u16 {
continue;
@@ -146,3 +159,24 @@ pub(crate) fn print_liner(
flush().unwrap();
}
}
pub(crate) fn print_liner_nopad(
transform: &RawTransform,
parent_transform: &RawTransform,
liner: &str,
text: &str,
) {
for (i, line) in text.lines().enumerate() {
if parent_transform.offset_y > transform.y + i as u16 {
continue;
}
let y = transform.y + i as u16 - parent_transform.offset_y;
if y >= parent_transform.height + parent_transform.y + parent_transform.py {
break;
}
print!("\x1b[{};{}H{liner}{line}\x1b[0m", y + 1, transform.x + 1);
flush().unwrap();
}
}