use yog_gfx::{Color, Rect};
use crate::layout::{Align, FlexDir};
mod builder;
mod make;
pub use crate::inventory_ids::{MACHINE_PREFIX, PLAYER_INVENTORY, SLOT_PREFIX};
pub use make::{
HOTBAR_GAP, button, grid, image, inv_slot, item_slot, label, machine, panel, player_inventory,
scroll_view, slot, slot_accepting, spacer, surface, text_box,
};
#[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,
pub action: Option<Action>,
pub visible: bool,
pub state: StateFlags,
pub states: Option<Box<StateStyles>>,
pub tooltip: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Action {
pub owner: u16,
pub slot: u16,
pub index: u32,
}
impl Action {
pub const SELECT: u16 = u16::MAX;
pub const fn new(slot: u16) -> Self {
Self {
owner: 0,
slot,
index: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct StateFlags(pub u8);
impl StateFlags {
pub const NONE: Self = Self(0);
pub const HOVER: Self = Self(1);
pub const PRESSED: Self = Self(2);
pub const FOCUSED: Self = Self(4);
pub const SELECTED: Self = Self(8);
pub const CHECKED: Self = Self(16);
pub const DISABLED: Self = Self(32);
pub const fn contains(self, o: Self) -> bool {
self.0 & o.0 == o.0
}
pub fn insert(&mut self, o: Self) {
self.0 |= o.0;
}
pub fn remove(&mut self, o: Self) {
self.0 &= !o.0;
}
pub fn set(&mut self, o: Self, on: bool) {
if on { self.insert(o) } else { self.remove(o) }
}
pub const fn union(self, o: Self) -> Self {
Self(self.0 | o.0)
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
}
impl std::ops::BitOr for StateFlags {
type Output = Self;
fn bitor(self, o: Self) -> Self {
self.union(o)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct StylePatch {
pub bg: Option<Color>,
pub bg_image: Option<String>,
pub color: Option<Color>,
pub shadow: Option<bool>,
pub focus_color: Option<Color>,
}
impl StylePatch {
pub fn is_empty(&self) -> bool {
*self == Self::default()
}
fn apply(&self, s: &mut Style) {
if let Some(v) = self.bg {
s.bg = v;
}
if let Some(v) = &self.bg_image {
s.bg_image.clone_from(v);
}
if let Some(v) = self.color {
s.color = v;
}
if let Some(v) = self.shadow {
s.text_shadow = v;
}
if let Some(v) = self.focus_color {
s.focus_color = v;
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct StateStyles {
pub rules: Vec<(StateFlags, StylePatch)>,
}
#[derive(Debug, Clone)]
pub enum WidgetKind {
Panel(FlexDir),
Grid(Box<GridSpec>),
Label(String),
Button(String),
ItemSlot(String),
InvSlot { item_id: String, count: u32 },
Image(Image),
Spacer,
Surface(String),
TextBox(TextBox),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GridLength {
Auto,
Px(f32),
Star(f32),
}
impl GridLength {
pub const STAR: Self = Self::Star(1.0);
pub fn parse(text: &str) -> Option<GridLength> {
let text = text.trim();
if text.eq_ignore_ascii_case(AUTO) {
return Some(GridLength::Auto);
}
if let Some(weight) = text.strip_suffix(STAR) {
let weight = weight.trim();
return if weight.is_empty() {
Some(GridLength::STAR)
} else {
weight.parse().ok().map(GridLength::Star)
};
}
text.parse().ok().map(GridLength::Px)
}
}
const AUTO: &str = "auto";
const STAR: char = '*';
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Track {
pub size: GridLength,
pub min: f32,
pub max: f32,
}
impl Track {
pub const fn new(size: GridLength) -> Self {
Self {
size,
min: 0.0,
max: f32::INFINITY,
}
}
pub const fn auto() -> Self {
Self::new(GridLength::Auto)
}
pub const fn px(px: f32) -> Self {
Self::new(GridLength::Px(px))
}
pub const fn star(weight: f32) -> Self {
Self::new(GridLength::Star(weight))
}
pub const fn min(mut self, v: f32) -> Self {
self.min = v;
self
}
pub const fn max(mut self, v: f32) -> Self {
self.max = v;
self
}
}
impl From<GridLength> for Track {
fn from(size: GridLength) -> Self {
Self::new(size)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct GridSpec {
pub rows: Vec<Track>,
pub cols: Vec<Track>,
pub row_spacing: f32,
pub col_spacing: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cell {
pub row: u16,
pub col: u16,
pub row_span: u16,
pub col_span: u16,
}
impl Default for Cell {
fn default() -> Self {
Self {
row: 0,
col: 0,
row_span: 1,
col_span: 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HAlign {
Left,
Center,
Right,
Stretch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VAlign {
Top,
Center,
Bottom,
Stretch,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Image {
pub source: String,
pub stretch: Stretch,
pub region: Option<Rect>,
pub tint: Color,
}
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 SLOT_SIZE: f32 = 18.0;
pub const SCREEN_W: f32 = 176.0;
pub const SCREEN_H: f32 = 166.0;
pub const DEFAULT_FOCUS_COLOR: Color = Color(0xFF_FFE040);
pub(crate) const TEXT_BOX_PAD_X: f32 = 4.0;
pub const PLACEHOLDER_COLOR: Color = Color(0xFF_707070);
pub const TEXT_BOX_BORDER: Color = Color(0xFF_A0A0A0);
pub const TEXT_BOX_TEXT: Color = Color(0xFF_E0E0E0);
pub const TEXT_BOX_FOCUS_BORDER: Color = Color(0xFF_FFFFFF);
pub const SELECTION_COLOR: Color = Color(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) }
}
}
const PERCENT: f32 = 100.0;
const MIN_SIZE: f32 = 4.0;
pub const DEFAULT_TEXT_COLOR: Color = Color(0xFF_CCCCAA);
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 / PERCENT),
}
}
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 cell: Cell,
pub halign: Option<HAlign>,
pub valign: Option<VAlign>,
pub bg: Color, pub bg_image: String,
pub color: Color, pub align: Align,
pub font_scale: f32,
pub text_shadow: bool, pub focus_style: FocusStyle,
pub focus_color: Color, 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: MIN_SIZE,
min_h: MIN_SIZE,
flex: 0.0,
dock: Dock::None,
gap: 2.0,
pad: [0.0; 4],
margin: [0.0; 4],
cell: Cell::default(),
halign: None,
valign: None,
bg: Color::TRANSPARENT,
bg_image: String::new(),
color: DEFAULT_TEXT_COLOR,
align: Align::Start,
font_scale: 1.0,
text_shadow: true,
focus_style: FocusStyle::default(),
focus_color: Color::TRANSPARENT,
no_wrap: false,
pos: None,
ellipsis: false,
clip: false,
scroll_y: 0.0,
scrollbar: false,
font: String::new(),
}
}
}