#![allow(non_snake_case)]
use godoru::{
Align, AppTheme, Color, Component, Container, ContainerDirection, ContainerWrap, CursorIcon,
GodoruApp, GodoruResult, ImageFit, Justify, Shader, SizeMode, WindowOptions,
};
#[derive(Clone, Debug)]
enum Action {
ButtonClick,
ButtonHover,
ButtonUnhover,
ButtonFocus,
ButtonBlur,
ButtonDown,
ButtonUp,
PanelClick,
TextHover,
ImageClick,
NameChanged(String),
NameSubmitted(String),
FeatureToggled(bool),
}
fn main() -> GodoruResult<()> {
let assets = godoru::Ui::<Action>::new().assets();
let logo = assets.image("godoru.png")?;
let neonShader = Shader::fromCode(
r#"
shader_type canvas_item;
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void fragment() {
COLOR = texture(TEXTURE, UV);
float hue = (UV.x+UV.y) * 0.25 + mod(TIME * 0.5, 1.0);
COLOR.rgb *= hsv2rgb(vec3(hue,0.6, 1.0));
}
"#,
);
let theme = AppTheme::new()
.backgroundColor(Color::rgb(0.0, 0.0, 0.0))
.class(Component::Container, "screen", |style| {
style.background(Color::rgb(0.02, 0.02, 0.08));
style.padding(24);
})
.class(Component::Container, "hero", |style| {
style.background(Color::rgb(1.0, 0.95, 0.0));
style.color(Color::rgb(0.0, 0.0, 0.0));
style.radius(18);
style.padding((18, 24, 18, 24));
style.border(Color::rgb(1.0, 0.0, 0.0), 6);
style.shadow(Color::rgba(1.0, 0.0, 1.0, 0.65), 20, 0.0, 10.0);
})
.class(Component::Container, "strip", |style| {
style.background(Color::rgb(0.0, 0.9, 1.0));
style.radius(14);
style.padding(14);
style.border(Color::rgb(0.0, 0.0, 1.0), 4);
})
.class(Component::Container, "card", |style| {
style.background(Color::rgb(1.0, 0.0, 0.0));
style.radius(16);
style.padding(18);
style.border(Color::rgb(0.0, 0.0, 1.0), 5);
style.shadow(Color::rgba(0.0, 1.0, 0.0, 0.75), 14, 6.0, 6.0);
})
.class(Component::Container, "shaderCard", |style| {
style.background(Color::rgb(0.0, 0.0, 1.0));
style.radius(18);
style.padding(18);
style.border(Color::rgb(1.0, 1.0, 1.0), 4);
style.shader(neonShader.clone());
})
.class(Component::Text, "title", |style| {
style.color(Color::rgb(0.0, 0.0, 0.0));
style.fontSize(36);
})
.class(Component::Text, "label", |style| {
style.color(Color::rgb(1.0, 1.0, 1.0));
style.fontSize(18);
})
.class(Component::Text, "blueText", |style| {
style.color(Color::rgb(0.0, 0.0, 1.0));
style.fontSize(24);
})
.class(Component::Image, "assetImage", |style| {
style.background(Color::rgb(0.0, 1.0, 0.0));
style.radius(18);
style.border(Color::rgb(1.0, 1.0, 0.0), 5);
})
.class(Component::Button, "primary", |style| {
style.background(Color::rgb(0.0, 0.0, 1.0));
style.color(Color::rgb(1.0, 1.0, 1.0));
style.radius(12);
style.padding((12, 22, 12, 22));
style.border(Color::rgb(1.0, 1.0, 1.0), 4);
})
.class(Component::Button, "primary:hover", |style| {
style.background(Color::rgb(0.0, 1.0, 0.0));
style.color(Color::rgb(0.0, 0.0, 0.0));
style.border(Color::rgb(1.0, 0.0, 1.0), 5);
})
.class(Component::Button, "primary:pressed", |style| {
style.background(Color::rgb(1.0, 0.0, 0.0));
style.color(Color::rgb(1.0, 1.0, 0.0));
style.border(Color::rgb(1.0, 1.0, 0.0), 6);
})
.class(Component::Button, "primary:focus", |style| {
style.background(Color::rgb(1.0, 0.0, 1.0));
style.color(Color::rgb(1.0, 1.0, 1.0));
style.border(Color::rgb(0.0, 1.0, 1.0), 6);
})
.class(Component::TextInput, "input", |style| {
style.background(Color::rgb(1.0, 1.0, 1.0));
style.color(Color::rgb(0.0, 0.0, 0.0));
style.radius(10);
style.padding((10, 14, 10, 14));
style.border(Color::rgb(0.0, 0.0, 1.0), 4);
style.fontSize(18);
})
.class(Component::TextInput, "input:focus", |style| {
style.background(Color::rgb(1.0, 1.0, 0.0));
style.border(Color::rgb(1.0, 0.0, 0.0), 6);
})
.class(Component::Checkbox, "check", |style| {
style.background(Color::rgb(0.0, 1.0, 1.0));
style.color(Color::rgb(0.0, 0.0, 0.0));
style.radius(10);
style.padding((10, 14, 10, 14));
style.border(Color::rgb(0.0, 0.0, 0.0), 4);
style.fontSize(18);
})
.class(Component::Checkbox, "check:focus", |style| {
style.background(Color::rgb(1.0, 0.0, 1.0));
style.color(Color::rgb(1.0, 1.0, 1.0));
})
.class(Component::Scroll, "scroll", |style| {
style.background(Color::rgb(0.2, 0.0, 0.55));
style.radius(14);
style.padding(12);
style.border(Color::rgb(0.0, 1.0, 0.0), 4);
});
let root = Container::<Action>::new()
.id("ComponentShowcaseRoot")
.class("screen")
.direction(ContainerDirection::Vertical)
.gap(16)
.children(|ui| {
ui.container()
.id("Hero")
.class("hero")
.direction(ContainerDirection::Vertical)
.gap(8)
.onClick(Action::PanelClick)
.cursor(CursorIcon::Pointer)
.children(|ui| {
ui.text("Godoru Component Showcase").class("title");
ui.text(
"Strong colors expose style mapping, decoration, state and layout issues.",
)
.class("blueText")
.onHover(Action::TextHover)
.cursor(CursorIcon::Pointer);
});
ui.container()
.id("DecorationsRow")
.class("strip")
.direction(ContainerDirection::Horizontal)
.wrap(ContainerWrap::Wrap)
.align(Align::Stretch)
.justify(Justify::SpaceBetween)
.gap(16)
.children(|ui| {
ui.container()
.id("RedCard")
.class("card")
.size(SizeMode::Fixed(320), SizeMode::Fit)
.direction(ContainerDirection::Vertical)
.gap(10)
.children(|ui| {
ui.text("Container").class("label");
ui.text("radius, border, shadow, padding, margin and click")
.class("label");
});
ui.container()
.id("ShaderCard")
.class("shaderCard")
.size(SizeMode::Fixed(320), SizeMode::Fit)
.direction(ContainerDirection::Vertical)
.gap(10)
.children(|ui| {
ui.text("Shader").class("label");
ui.text("CanvasItem shader applied to a container")
.class("label");
});
});
ui.container()
.id("ComponentsGrid")
.direction(ContainerDirection::Horizontal)
.wrap(ContainerWrap::Wrap)
.gap(16)
.children(|ui| {
ui.container()
.class("card")
.size(SizeMode::Fixed(360), SizeMode::Fit)
.direction(ContainerDirection::Vertical)
.gap(12)
.children(|ui| {
ui.text("Text").class("label").size(26);
ui.text("White label text").class("label");
ui.text("Blue text with hover event")
.class("blueText")
.onHover(Action::TextHover)
.cursor(CursorIcon::Pointer);
});
ui.container()
.class("card")
.size(SizeMode::Fixed(360), SizeMode::Fit)
.direction(ContainerDirection::Vertical)
.gap(12)
.children(|ui| {
ui.text("Image").class("label").size(26);
ui.image(logo)
.class("assetImage")
.fit(ImageFit::Contain)
.customMinimumSize(220, 140)
.onClick(Action::ImageClick)
.cursor(CursorIcon::Pointer);
});
ui.container()
.class("card")
.size(SizeMode::Fixed(360), SizeMode::Fit)
.direction(ContainerDirection::Vertical)
.gap(12)
.children(|ui| {
ui.text("Button").class("label").size(26);
ui.button("Hover, focus, press")
.class("primary")
.customMinimumSize(260, 56)
.cursor(CursorIcon::Pointer)
.onClick(Action::ButtonClick)
.onHover(Action::ButtonHover)
.onUnhover(Action::ButtonUnhover)
.onFocus(Action::ButtonFocus)
.onBlur(Action::ButtonBlur)
.onPointerDown(Action::ButtonDown)
.onPointerUp(Action::ButtonUp);
});
ui.container()
.class("card")
.size(SizeMode::Fixed(360), SizeMode::Fit)
.direction(ContainerDirection::Vertical)
.gap(12)
.children(|ui| {
ui.text("TextInput + Checkbox").class("label").size(26);
ui.textInput("")
.class("input")
.placeholder("Type here")
.customMinimumSize(280, 48)
.onChange(Action::NameChanged)
.onSubmit(Action::NameSubmitted)
.onFocus(Action::ButtonFocus)
.onBlur(Action::ButtonBlur);
ui.checkbox(false)
.class("check")
.text("Enable strong mode")
.customMinimumSize(280, 48)
.onToggle(Action::FeatureToggled);
});
});
ui.scroll()
.id("ScrollPreview")
.class("scroll")
.customMinimumSize(760, 190)
.children(|ui| {
for index in 1..=10 {
ui.container()
.class(if index % 2 == 0 { "strip" } else { "card" })
.margin((4, 0, 4, 0))
.padding(10)
.children(|ui| {
ui.text(format!(
"Scroll row {index}: Container + Text inside Scroll"
))
.class("label");
});
}
});
});
GodoruApp::<(), Action>::withState(())
.assetsRoot("assets")
.onAction(|_, action| match action {
Action::NameChanged(value) => println!("Name changed: {value}"),
Action::NameSubmitted(value) => println!("Name submitted: {value}"),
Action::FeatureToggled(value) => println!("Feature toggled: {value}"),
other => println!("Godoru showcase action: {other:?}"),
})
.createWindow(
WindowOptions::new("component_showcase")
.title("Godoru Component Showcase")
.size(800, 800)
.theme(theme)
.root(root),
)?
.run()
}