Lots of owork on a calculator example, transparency, etc.
This commit is contained in:
161
examples/calculator/src/main.rs
Normal file
161
examples/calculator/src/main.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
mod components;
|
||||
mod state;
|
||||
|
||||
use ruin_app::prelude::*;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::{EnvFilter, fmt};
|
||||
|
||||
use components::{Display, Keypad, ModeToggle, ScientificPanel};
|
||||
use state::{CalcMode, CalcState};
|
||||
|
||||
#[ruin_runtime::async_main]
|
||||
async fn main() -> ruin_app::Result<()> {
|
||||
init_tracing();
|
||||
|
||||
App::new()
|
||||
.window(
|
||||
Window::new()
|
||||
.title("Calculator")
|
||||
.app_id("dev.ruin.calculator")
|
||||
.base_color(Color::rgba(255, 255, 255, 80))
|
||||
.resizable(false),
|
||||
)
|
||||
.mount(view! { CalculatorApp() {} })
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
fn init_tracing() {
|
||||
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
EnvFilter::new(
|
||||
"info,ruin_app::resize=trace,ruin_app::interaction=trace,\
|
||||
ruin_ui_platform_wayland::resize=trace,ruin_ui_platform_wayland::perf=debug,\
|
||||
ruin_ui_platform_wayland::event_bridge=trace",
|
||||
)
|
||||
});
|
||||
|
||||
let _ = tracing_subscriber::registry()
|
||||
.with(fmt::layer().with_target(true).without_time())
|
||||
.with(filter)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn CalculatorApp() -> impl IntoView {
|
||||
let state = CalcState::new();
|
||||
|
||||
// ---- Keyboard shortcuts (application-scoped) ----
|
||||
|
||||
for ch in "0123456789.+-*/%()".chars() {
|
||||
let s = state.clone();
|
||||
let display = match ch {
|
||||
'*' => "×",
|
||||
'/' => "÷",
|
||||
_ => "",
|
||||
};
|
||||
let text = if display.is_empty() {
|
||||
ch.to_string()
|
||||
} else {
|
||||
display.to_string()
|
||||
};
|
||||
use_shortcut(Shortcut::new(Key::Character(ch)), ShortcutScope::Application, {
|
||||
let text = text.clone();
|
||||
move || s.input(&text)
|
||||
});
|
||||
}
|
||||
|
||||
// = key also evaluates
|
||||
{
|
||||
let s = state.clone();
|
||||
use_shortcut(Shortcut::new(Key::Character('=')), ShortcutScope::Application, move || s.evaluate());
|
||||
}
|
||||
{
|
||||
let s = state.clone();
|
||||
use_shortcut(Shortcut::new(Key::Enter), ShortcutScope::Application, move || s.evaluate());
|
||||
}
|
||||
{
|
||||
let s = state.clone();
|
||||
use_shortcut(Shortcut::new(Key::Backspace), ShortcutScope::Application, move || s.backspace());
|
||||
}
|
||||
{
|
||||
let s = state.clone();
|
||||
use_shortcut(Shortcut::new(Key::Escape), ShortcutScope::Application, move || s.clear());
|
||||
}
|
||||
{
|
||||
let s = state.clone();
|
||||
use_shortcut(Shortcut::new(Key::Delete), ShortcutScope::Application, move || s.clear());
|
||||
}
|
||||
|
||||
// ---- Layout ----
|
||||
|
||||
let mode = state.mode.clone();
|
||||
|
||||
let text_style = PartialTextStyle {
|
||||
color: Some(Color::BLACK),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if mode.get() == CalcMode::Scientific {
|
||||
view! {
|
||||
column(
|
||||
gap = 10.0,
|
||||
padding = 12.0,
|
||||
align_items = AlignItems::Stretch,
|
||||
text_style = text_style,
|
||||
) {
|
||||
Display(state = state.clone()) {}
|
||||
ModeToggle(state = state.clone()) {}
|
||||
ScientificPanel(state = state.clone()) {}
|
||||
Keypad(state = state.clone()) {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
view! {
|
||||
column(
|
||||
gap = 10.0,
|
||||
padding = 12.0,
|
||||
align_items = AlignItems::Stretch,
|
||||
text_style = text_style,
|
||||
) {
|
||||
Display(state = state.clone()) {}
|
||||
ModeToggle(state = state.clone()) {}
|
||||
Keypad(state = state.clone()) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[component]
|
||||
fn StateProbe(slot: Rc<RefCell<Option<CalcState>>>) -> impl IntoView {
|
||||
let state = CalcState::new();
|
||||
*slot.borrow_mut() = Some(state);
|
||||
block().children(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_evaluation_replaces_expression_and_preserves_history() {
|
||||
let slot = Rc::new(RefCell::new(None::<CalcState>));
|
||||
let _ = ruin_app::__render_mountable_for_test(&view! {
|
||||
StateProbe(slot = Rc::clone(&slot)) {}
|
||||
});
|
||||
|
||||
let state = slot.borrow().clone().expect("state should be captured");
|
||||
let _ = state.expression.set("2+2".to_string());
|
||||
state.evaluate();
|
||||
|
||||
assert_eq!(state.expression.get(), "4");
|
||||
assert_eq!(state.result.get(), "");
|
||||
assert_eq!(state.is_error.get(), false);
|
||||
assert_eq!(state.history.get().len(), 1);
|
||||
assert_eq!(state.history.get()[0].expression, "2+2");
|
||||
assert_eq!(state.history.get()[0].result, "4");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user