1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-07-22 17:18:39 +00:00

Add button, toggle, and option elements

This commit is contained in:
v-rob 2024-02-07 22:48:56 -08:00
parent c7fca1b956
commit 68612c6900
9 changed files with 407 additions and 0 deletions

View file

@ -0,0 +1,106 @@
-- Luanti
-- SPDX-License-Identifier: LGPL-2.1-or-later
-- Copyright (C) 2024 v-rob, Vincent Robinson <robinsonvincent89@gmail.com>
ui.Button = ui._new_type(ui.Elem, "button", 0x02, true)
function ui.Button:_init(props)
ui.Elem._init(self, props)
self._disabled = ui._opt(props.disabled, "boolean")
self._on_press = ui._opt(props.on_press, "function")
end
function ui.Button:_encode_fields()
local fl = ui._make_flags()
ui._shift_flag(fl, self._disabled)
ui._shift_flag(fl, self._on_press)
return ui._encode("SZ", ui.Elem._encode_fields(self), ui._encode_flags(fl))
end
ui.Button._handlers[0x00] = function(self, ev, data)
return self._on_press
end
ui.Toggle = ui._new_type(ui.Elem, "toggle", 0x03, true)
ui.Check = ui.derive_elem(ui.Toggle, "check")
ui.Switch = ui.derive_elem(ui.Toggle, "switch")
function ui.Toggle:_init(props)
ui.Elem._init(self, props)
self._disabled = ui._opt(props.disabled, "boolean")
self._selected = ui._opt(props.selected, "boolean")
self._on_press = ui._opt(props.on_press, "function")
self._on_change = ui._opt(props.on_change, "function")
end
function ui.Toggle:_encode_fields()
local fl = ui._make_flags()
ui._shift_flag(fl, self._disabled)
ui._shift_flag_bool(fl, self._selected)
ui._shift_flag(fl, self._on_press)
ui._shift_flag(fl, self._on_change)
return ui._encode("SZ", ui.Elem._encode_fields(self), ui._encode_flags(fl))
end
ui.Toggle._handlers[0x00] = function(self, ev, data)
return self._on_press
end
ui.Toggle._handlers[0x01] = function(self, ev, data)
local selected = ui._decode("B", data)
ev.selected = selected ~= 0
return self._on_change
end
ui.Option = ui._new_type(ui.Elem, "option", 0x04, true)
ui.Radio = ui.derive_elem(ui.Option, "radio")
function ui.Option:_init(props)
ui.Elem._init(self, props)
self._disabled = ui._opt(props.disabled, "boolean")
self._selected = ui._opt(props.selected, "boolean")
self._family = ui._opt(props.family, "id")
self._on_press = ui._opt(props.on_press, "function")
self._on_change = ui._opt(props.on_change, "function")
end
function ui.Option:_encode_fields()
local fl = ui._make_flags()
ui._shift_flag(fl, self._disabled)
ui._shift_flag_bool(fl, self._selected)
if ui._shift_flag(fl, self._family) then
ui._encode_flag(fl, "z", self._family)
end
ui._shift_flag(fl, self._on_press)
ui._shift_flag(fl, self._on_change)
return ui._encode("SZ", ui.Elem._encode_fields(self), ui._encode_flags(fl))
end
ui.Option._handlers[0x00] = function(self, ev, data)
return self._on_press
end
ui.Option._handlers[0x01] = function(self, ev, data)
local selected = ui._decode("B", data)
ev.selected = selected ~= 0
return self._on_change
end

View file

@ -11,6 +11,7 @@ dofile(UI_PATH .. "selector.lua")
dofile(UI_PATH .. "style.lua")
dofile(UI_PATH .. "elem.lua")
dofile(UI_PATH .. "clickable_elems.lua")
dofile(UI_PATH .. "static_elems.lua")
dofile(UI_PATH .. "window.lua")

View file

@ -246,6 +246,19 @@ func_preds["nth_last_match"] = function(str)
end
end
func_preds["family"] = function(family)
if family == "*" then
return function(elem)
return result(elem._family ~= nil)
end
end
assert(ui.is_id(family), "Expected '*' or ID string for ?family()")
return function(elem)
return result(elem._family == family)
end
end
local function parse_term(str, pred)
str = str:trim()
assert(str ~= "", "Expected selector term")

View file

@ -19,6 +19,10 @@ local prelude_theme = ui.Style {
ui.Style "image" {
icon_scale = 0,
},
ui.Style "check, switch, radio" {
icon_place = "left",
text_align = "left",
},
}
function ui.get_prelude_theme()

View file

@ -1,5 +1,6 @@
set(ui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/box.cpp
${CMAKE_CURRENT_SOURCE_DIR}/clickable_elems.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/manager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/static_elems.cpp

173
src/ui/clickable_elems.cpp Normal file
View file

@ -0,0 +1,173 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 v-rob, Vincent Robinson <robinsonvincent89@gmail.com>
#include "ui/clickable_elems.h"
#include "debug.h"
#include "log.h"
#include "ui/manager.h"
#include "util/serialize.h"
namespace ui
{
void Button::reset()
{
Elem::reset();
m_disabled = false;
}
void Button::read(std::istream &is)
{
auto super = newIs(readStr32(is));
Elem::read(super);
u32 set_mask = readU32(is);
m_disabled = testShift(set_mask);
if (testShift(set_mask))
enableEvent(ON_PRESS);
}
bool Button::processInput(const SDL_Event &event)
{
return getMain().processFullPress(event, UI_CALLBACK(onPress));
}
void Button::onPress()
{
if (!m_disabled && testEvent(ON_PRESS)) {
g_manager.sendMessage(createEvent(ON_PRESS).str());
}
}
void Toggle::reset()
{
Elem::reset();
m_disabled = false;
}
void Toggle::read(std::istream &is)
{
auto super = newIs(readStr32(is));
Elem::read(super);
u32 set_mask = readU32(is);
m_disabled = testShift(set_mask);
testShiftBool(set_mask, m_selected);
if (testShift(set_mask))
enableEvent(ON_PRESS);
if (testShift(set_mask))
enableEvent(ON_CHANGE);
}
bool Toggle::processInput(const SDL_Event &event)
{
return getMain().processFullPress(event, UI_CALLBACK(onPress));
}
void Toggle::onPress()
{
if (m_disabled) {
return;
}
m_selected = !m_selected;
// Send both a press and a change event since both occurred.
if (testEvent(ON_PRESS)) {
g_manager.sendMessage(createEvent(ON_PRESS).str());
}
if (testEvent(ON_CHANGE)) {
auto os = createEvent(ON_CHANGE);
writeU8(os, m_selected);
g_manager.sendMessage(os.str());
}
}
void Option::reset()
{
Elem::reset();
m_disabled = false;
m_family.clear();
}
void Option::read(std::istream &is)
{
auto super = newIs(readStr32(is));
Elem::read(super);
u32 set_mask = readU32(is);
m_disabled = testShift(set_mask);
testShiftBool(set_mask, m_selected);
if (testShift(set_mask))
m_family = readNullStr(is);
if (testShift(set_mask))
enableEvent(ON_PRESS);
if (testShift(set_mask))
enableEvent(ON_CHANGE);
}
bool Option::processInput(const SDL_Event &event)
{
return getMain().processFullPress(event, UI_CALLBACK(onPress));
}
void Option::onPress()
{
if (m_disabled) {
return;
}
// Send a press event for this pressed option button.
if (testEvent(ON_PRESS)) {
g_manager.sendMessage(createEvent(ON_PRESS).str());
}
// Select this option button unconditionally before deselecting the
// others in the family.
onChange(true);
// If this option button has no family, then don't do anything else
// since there may be other buttons with the same empty family string.
if (m_family.empty()) {
return;
}
// If we find any other option buttons in this family, deselect them.
for (Elem *elem : getWindow().getElems()) {
if (elem->getType() != getType()) {
continue;
}
Option *option = (Option *)elem;
if (option->m_family == m_family && option != this) {
option->onChange(false);
}
}
}
void Option::onChange(bool selected)
{
bool was_selected = m_selected;
m_selected = selected;
// If the state of the option button changed, send a change event.
if (was_selected != m_selected && testEvent(ON_CHANGE)) {
auto os = createEvent(ON_CHANGE);
writeU8(os, m_selected);
g_manager.sendMessage(os.str());
}
}
}

102
src/ui/clickable_elems.h Normal file
View file

@ -0,0 +1,102 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 v-rob, Vincent Robinson <robinsonvincent89@gmail.com>
#pragma once
#include "ui/box.h"
#include "ui/elem.h"
#include "ui/helpers.h"
#include <iostream>
#include <string>
namespace ui
{
class Button : public Elem
{
private:
// Serialized constants; do not change values of entries.
static constexpr u32 ON_PRESS = 0x00;
bool m_disabled;
public:
Button(Window &window, std::string id) :
Elem(window, std::move(id))
{}
virtual Type getType() const override { return BUTTON; }
virtual void reset() override;
virtual void read(std::istream &is) override;
virtual bool isBoxDisabled(const Box &box) const override { return m_disabled; }
virtual bool processInput(const SDL_Event &event) override;
private:
void onPress();
};
class Toggle : public Elem
{
private:
// Serialized constants; do not change values of entries.
static constexpr u32 ON_PRESS = 0x00;
static constexpr u32 ON_CHANGE = 0x01;
bool m_disabled;
bool m_selected = false; // Persistent
public:
Toggle(Window &window, std::string id) :
Elem(window, std::move(id))
{}
virtual Type getType() const override { return TOGGLE; }
virtual void reset() override;
virtual void read(std::istream &is) override;
virtual bool isBoxSelected(const Box &box) const override { return m_selected; }
virtual bool isBoxDisabled(const Box &box) const override { return m_disabled; }
virtual bool processInput(const SDL_Event &event) override;
private:
void onPress();
};
class Option : public Elem
{
private:
// Serialized constants; do not change values of entries.
static constexpr u32 ON_PRESS = 0x00;
static constexpr u32 ON_CHANGE = 0x01;
bool m_disabled;
std::string m_family;
bool m_selected = false; // Persistent
public:
Option(Window &window, std::string id) :
Elem(window, std::move(id))
{}
virtual Type getType() const override { return OPTION; }
virtual void reset() override;
virtual void read(std::istream &is) override;
virtual bool isBoxSelected(const Box &box) const override { return m_selected; }
virtual bool isBoxDisabled(const Box &box) const override { return m_disabled; }
virtual bool processInput(const SDL_Event &event) override;
private:
void onPress();
void onChange(bool selected);
};
}

View file

@ -11,6 +11,7 @@
#include "util/serialize.h"
// Include every element header for Elem::create()
#include "ui/clickable_elems.h"
#include "ui/static_elems.h"
#include <SDL2/SDL.h>
@ -29,6 +30,9 @@ namespace ui
switch (type) {
CREATE(ELEM, Elem);
CREATE(ROOT, Root);
CREATE(BUTTON, Button);
CREATE(TOGGLE, Toggle);
CREATE(OPTION, Option);
default:
return nullptr;
}

View file

@ -32,6 +32,9 @@ namespace ui
{
ELEM = 0x00,
ROOT = 0x01,
BUTTON = 0x02,
TOGGLE = 0x03,
OPTION = 0x04,
};
// The main box is always the zeroth item in the Box::NO_GROUP group.