use crate::layout::{Align, FlexDir};
#[derive(Debug, Clone)]
pub struct Widget {
pub kind: WidgetKind,
pub id: Option<String>,
pub style: Style,
pub flex_dir: FlexDir,
pub children: Vec<Widget>,
pub on_click: Option<String>,
pub enabled: bool,
pub focused: bool,
}
#[derive(Debug, Clone)]
pub enum WidgetKind {
Panel(FlexDir),
Label(String),
Button(String),
ItemSlot(String),
InvSlot { item_id: String, count: u32 },
McImage { id: String, img_w: f32, img_h: f32 },
Image(Image),
Spacer,
TextBox(TextBox),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Image {
pub source: String,
pub stretch: Stretch,
pub region: Option<[f32; 4]>,
pub tint: u32,
}
pub use yog_gfx::image::Stretch;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct TextBox {
pub text: String,
pub placeholder: String,
pub max_length: usize,
pub on_change: Option<String>,
pub on_submit: Option<String>,
pub caret: Option<usize>,
pub caret_visible: bool,
pub selection: (usize, usize),
pub first: usize,
}
pub const PLACEHOLDER_COLOR: u32 = 0xFF_707070;
pub const TEXT_BOX_BORDER: u32 = 0xFF_A0A0A0;
pub const TEXT_BOX_FOCUS_BORDER: u32 = 0xFF_FFFFFF;
pub const SELECTION_COLOR: u32 = 0xFF_2850A0;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Dock {
#[default]
None,
Fill,
Left,
Right,
Top,
Bottom,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum FocusStyle {
#[default]
Outline,
Fill,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Units {
#[default]
Auto,
Px(f32),
Percent(f32),
}
impl From<f32> for Units {
fn from(v: f32) -> Self {
if v <= 0.0 {
Units::Auto
} else {
Units::Px(v)
}
}
}
impl Units {
pub fn resolve(self, avail: f32) -> Option<f32> {
match self {
Units::Auto => None,
Units::Px(v) => Some(v),
Units::Percent(p) => Some(avail * p / 100.0),
}
}
pub fn is_auto(self) -> bool {
matches!(self, Units::Auto)
}
}
#[derive(Debug, Clone)]
pub struct Style {
pub w: Units, pub h: Units, pub min_w: f32,
pub min_h: f32,
pub flex: f32, pub dock: Dock, pub gap: f32, pub pad: [f32; 4], pub margin: [f32; 4], pub bg: u32, pub color: u32, pub align: Align,
pub font_scale: f32,
pub text_shadow: bool, pub focus_style: FocusStyle,
pub focus_color: u32, pub no_wrap: bool,
pub pos: Option<(f32, f32)>,
pub ellipsis: bool,
pub clip: bool,
pub scroll_y: f32,
pub scrollbar: bool,
pub font: String,
}
impl Default for Style {
fn default() -> Self {
Self {
w: Units::Auto,
h: Units::Auto,
min_w: 4.0,
min_h: 4.0,
flex: 0.0,
dock: Dock::None,
gap: 2.0,
pad: [0.0; 4],
margin: [0.0; 4],
bg: 0,
color: 0xFF_CCCCAA,
align: Align::Start,
font_scale: 1.0,
text_shadow: true,
focus_style: FocusStyle::default(),
focus_color: 0,
no_wrap: false,
pos: None,
ellipsis: false,
clip: false,
scroll_y: 0.0,
scrollbar: false,
font: String::new(),
}
}
}
impl Widget {
fn new(kind: WidgetKind) -> Self {
let flex_dir = if let WidgetKind::Panel(dir) = &kind {
*dir
} else {
FlexDir::Column
};
Self {
kind,
id: None,
style: Style::default(),
flex_dir,
children: Vec::new(),
on_click: None,
enabled: true,
focused: false,
}
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn on_click(mut self, ev: impl Into<String>) -> Self {
self.on_click = Some(ev.into());
self
}
pub fn child(mut self, w: Widget) -> Self {
self.children.push(w);
self
}
pub fn w(mut self, w: impl Into<Units>) -> Self {
self.style.w = w.into();
self
}
pub fn h(mut self, h: impl Into<Units>) -> Self {
self.style.h = h.into();
self
}
pub fn min_w(mut self, v: f32) -> Self {
self.style.min_w = v;
self
}
pub fn min_h(mut self, v: f32) -> Self {
self.style.min_h = v;
self
}
pub fn flex(mut self, v: f32) -> Self {
self.style.flex = v;
self
}
pub fn flex_dir(mut self, v: FlexDir) -> Self {
self.flex_dir = v;
self
}
pub fn gap(mut self, v: f32) -> Self {
self.style.gap = v;
self
}
pub fn bg(mut self, v: u32) -> Self {
self.style.bg = v;
self
}
pub fn color(mut self, v: u32) -> Self {
self.style.color = v;
self
}
pub fn align(mut self, v: Align) -> Self {
self.style.align = v;
self
}
pub fn font_scale(mut self, v: f32) -> Self {
self.style.font_scale = v;
self
}
pub fn shadow(mut self, v: bool) -> Self {
self.style.text_shadow = v;
self
}
pub fn no_wrap(mut self) -> Self {
self.style.no_wrap = true;
self
}
pub fn focus_style(mut self, v: FocusStyle) -> Self {
self.style.focus_style = v;
self
}
pub fn focus_color(mut self, v: u32) -> Self {
self.style.focus_color = v;
self
}
pub fn dock(mut self, v: Dock) -> Self {
self.style.dock = v;
self
}
pub fn enabled(mut self, v: bool) -> Self {
self.enabled = v;
self
}
pub fn focused(mut self, v: bool) -> Self {
self.focused = v;
self
}
pub fn at(mut self, x: f32, y: f32) -> Self {
self.style.pos = Some((x, y));
self
}
pub fn ellipsis(mut self) -> Self {
self.style.no_wrap = true;
self.style.ellipsis = true;
self
}
pub fn clip(mut self, v: bool) -> Self {
self.style.clip = v;
self
}
pub fn scroll_y(mut self, v: f32) -> Self {
self.style.scroll_y = v;
self
}
pub fn scrollbar(mut self, v: bool) -> Self {
self.style.scrollbar = v;
self
}
pub fn font(mut self, font: impl Into<String>) -> Self {
self.style.font = font.into();
self
}
pub fn placeholder(mut self, text: impl Into<String>) -> Self {
if let WidgetKind::TextBox(t) = &mut self.kind {
t.placeholder = text.into();
}
self
}
pub fn max_length(mut self, n: usize) -> Self {
if let WidgetKind::TextBox(t) = &mut self.kind {
t.max_length = n;
}
self
}
pub fn on_change(mut self, ev: impl Into<String>) -> Self {
if let WidgetKind::TextBox(t) = &mut self.kind {
t.on_change = Some(ev.into());
}
self
}
pub fn on_submit(mut self, ev: impl Into<String>) -> Self {
if let WidgetKind::TextBox(t) = &mut self.kind {
t.on_submit = Some(ev.into());
}
self
}
pub fn stretch(mut self, stretch: Stretch) -> Self {
if let WidgetKind::Image(i) = &mut self.kind {
i.stretch = stretch;
}
self
}
pub fn region(mut self, region: [f32; 4]) -> Self {
if let WidgetKind::Image(i) = &mut self.kind {
i.region = Some(region);
}
self
}
pub fn tint(mut self, tint: u32) -> Self {
if let WidgetKind::Image(i) = &mut self.kind {
i.tint = tint;
}
self
}
pub fn children(mut self, children: impl IntoIterator<Item = Widget>) -> Self {
self.children.extend(children);
self
}
pub fn find_mut(&mut self, id: &str) -> Option<&mut Widget> {
if self.id.as_deref() == Some(id) {
return Some(self);
}
self.children.iter_mut().find_map(|c| c.find_mut(id))
}
pub fn set_text(&mut self, text: impl Into<String>) -> bool {
match &mut self.kind {
WidgetKind::Label(t) | WidgetKind::Button(t) => {
*t = text.into();
true
}
WidgetKind::TextBox(t) => {
t.text = text.into();
true
}
_ => false,
}
}
pub fn padding(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
self.style.pad = [top, right, bottom, left];
self
}
pub fn margin(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
self.style.margin = [top, right, bottom, left];
self
}
}
pub fn panel(dir: FlexDir) -> Widget {
Widget::new(WidgetKind::Panel(dir))
}
pub fn scroll_view() -> Widget {
panel(FlexDir::Column).clip(true).scrollbar(true)
}
pub fn label(text: impl Into<String>) -> Widget {
Widget::new(WidgetKind::Label(text.into()))
}
pub fn button(text: impl Into<String>) -> Widget {
Widget::new(WidgetKind::Button(text.into()))
}
pub fn item_slot(item_id: impl Into<String>) -> Widget {
Widget::new(WidgetKind::ItemSlot(item_id.into()))
}
pub fn inv_slot(item_id: impl Into<String>, count: u32) -> Widget {
Widget::new(WidgetKind::InvSlot {
item_id: item_id.into(),
count,
})
.w(18.0)
.h(18.0)
}
pub fn mc_image(id: impl Into<String>, img_w: f32, img_h: f32) -> Widget {
Widget::new(WidgetKind::McImage {
id: id.into(),
img_w,
img_h,
})
.w(img_w)
.h(img_h)
}
pub fn image(source: impl Into<String>) -> Widget {
Widget::new(WidgetKind::Image(Image {
source: source.into(),
stretch: Stretch::Uniform,
region: None,
tint: 0xFFFF_FFFF,
}))
}
pub fn spacer() -> Widget {
Widget::new(WidgetKind::Spacer)
}
pub fn text_box(text: impl Into<String>) -> Widget {
Widget::new(WidgetKind::TextBox(TextBox {
text: text.into(),
..TextBox::default()
}))
.bg(0xFF_000000)
.color(0xFF_E0E0E0)
.padding(0.0, 4.0, 0.0, 4.0)
}