yog_ui/widget/make.rs
1//! The widgets, made: a constructor per kind.
2
3use super::{
4 Image, SLOT_SIZE, Stretch, TEXT_BOX_PAD_X, TEXT_BOX_TEXT, TextBox, Widget, WidgetKind,
5};
6use yog_gfx::Color;
7
8use crate::layout::FlexDir;
9
10pub fn panel(dir: FlexDir) -> Widget {
11 Widget::new(WidgetKind::Panel(dir))
12}
13/// WPF's `Grid`: a container of rows and columns — give it [`Widget::rows`]
14/// and [`Widget::cols`] ([`Track`]s: pixels, `Auto` or `*` shares) and put
15/// each child in a cell with [`Widget::cell`] / [`Widget::span`]. A child
16/// fills its cell unless it has [`Widget::halign`] / [`Widget::valign`] (or
17/// an explicit size); [`Widget::margin`] insets it.
18///
19/// ```
20/// use yog_ui::widget::{self, GridLength, Track};
21/// let ui = widget::grid()
22/// .rows([Track::auto(), Track::star(1.0), Track::px(20.0)])
23/// .cols([GridLength::Px(80.0), GridLength::STAR])
24/// .child(widget::label("Title").cell(0, 0).span(1, 2))
25/// .child(widget::scroll_view().cell(1, 0))
26/// .child(widget::label("Details").cell(1, 1))
27/// .child(widget::button("OK").cell(2, 1));
28/// ```
29pub fn grid() -> Widget {
30 Widget::new(WidgetKind::Grid(Box::default()))
31}
32/// A vertically scrolling column: clips its children, scrolls them by
33/// [`Widget::scroll_y`] and draws a scrollbar when they overflow — WPF's
34/// `ScrollViewer` around a `StackPanel`. Give it a fixed (or flex) height.
35pub fn scroll_view() -> Widget {
36 panel(FlexDir::Column).clip(true).scrollbar(true)
37}
38pub fn label(text: impl Into<String>) -> Widget {
39 Widget::new(WidgetKind::Label(text.into()))
40}
41pub fn button(text: impl Into<String>) -> Widget {
42 Widget::new(WidgetKind::Button(text.into()))
43}
44pub fn item_slot(item_id: impl Into<String>) -> Widget {
45 Widget::new(WidgetKind::ItemSlot(item_id.into()))
46}
47/// An inventory-slot decoration (icon + stack-count overlay) for `item_id`
48/// (empty string = empty slot) with `count` items. Purely decorative — real
49/// drag-and-drop still goes through the vanilla `Slot` this overlays.
50pub fn inv_slot(item_id: impl Into<String>, count: u32) -> Widget {
51 Widget::new(WidgetKind::InvSlot {
52 item_id: item_id.into(),
53 count,
54 })
55 .w(SLOT_SIZE)
56 .h(SLOT_SIZE)
57}
58use crate::inventory_ids::PLAYER_INVENTORY;
59
60/// Marks an inventory screen as a machine making recipes of `kind`: its
61/// `places` (`(kind's slot, index, inventory slot)`) and its `result` slot
62/// — what a `.yui` `<RecipeArea>` becomes. Draws nothing.
63pub fn machine(kind: &str, places: &[(&str, usize, usize)], result: usize) -> Widget {
64 spacer()
65 .w(0.0)
66 .h(0.0)
67 .id(crate::inventory_ids::MachineId::write(kind, places, result))
68}
69/// Gap between the player's main inventory and their hotbar (vanilla).
70pub const HOTBAR_GAP: f32 = 4.0;
71
72/// Slot `index` of an inventory screen's container: the frame its real
73/// slot sits in — the game draws the item, the hover and the drag over it.
74/// Where it lands in the layout is where the slot is.
75pub fn slot(index: usize) -> Widget {
76 inv_slot("", 0).id(crate::inventory_ids::slot_id(index, &[]))
77}
78/// A [`slot`] that only takes `accepts`: item ids (`minecraft:redstone`)
79/// and item tags (`#c:dusts`).
80pub fn slot_accepting(index: usize, accepts: &[&str]) -> Widget {
81 inv_slot("", 0).id(crate::inventory_ids::slot_id(index, accepts))
82}
83/// The player's inventory on an inventory screen: 3 rows of 9, then the
84/// hotbar [`HOTBAR_GAP`] below — frames for the real slots, like [`slot`].
85pub fn player_inventory() -> Widget {
86 let row = || {
87 (0..9).fold(panel(FlexDir::Row).gap(0.0), |r, _| {
88 r.child(inv_slot("", 0))
89 })
90 };
91 panel(FlexDir::Column)
92 .id(PLAYER_INVENTORY)
93 .gap(0.0)
94 .child(row())
95 .child(row())
96 .child(row())
97 .child(row().margin(HOTBAR_GAP, 0.0, 0.0, 0.0))
98}
99/// An image from a Yog image source — `"item:apple"`, `"block:stone"`,
100/// `"sprite:button"`, `"mymod:gui/logo"` (see [`yog_gfx::image`]) — the
101/// host resolves it for its Minecraft version. Sized to the image's natural
102/// size (16×16 until the host told it) unless given `w`/`h`; scaled into the
103/// widget per [`Widget::stretch`] (default `Uniform`), nearest-neighbour.
104pub fn image(source: impl Into<String>) -> Widget {
105 Widget::new(WidgetKind::Image(Image {
106 source: source.into(),
107 stretch: Stretch::Uniform,
108 region: None,
109 tint: Color::WHITE,
110 }))
111}
112pub fn spacer() -> Widget {
113 Widget::new(WidgetKind::Spacer)
114}
115/// A box drawn by the code — the [`Surface`](crate::Surface) registered
116/// under `id` with [`crate::surface::register`]. No natural size: give it
117/// `w`/`h` (or let a parent stretch it).
118pub fn surface(id: impl Into<String>) -> Widget {
119 Widget::new(WidgetKind::Surface(id.into()))
120}
121/// A single-line text input holding `text` — black box, grey border (white
122/// while focused), like vanilla's `EditBox`. Its text, caret and focus live
123/// in a [`TextBoxState`](crate::TextBoxState) that the code-behind keeps and
124/// applies to it each frame.
125pub fn text_box(text: impl Into<String>) -> Widget {
126 Widget::new(WidgetKind::TextBox(TextBox {
127 text: text.into(),
128 ..TextBox::default()
129 }))
130 .bg(Color::BLACK)
131 .color(TEXT_BOX_TEXT)
132 .padding(0.0, TEXT_BOX_PAD_X, 0.0, TEXT_BOX_PAD_X)
133}