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:
parent
c7fca1b956
commit
68612c6900
9 changed files with 407 additions and 0 deletions
106
builtin/ui/clickable_elems.lua
Normal file
106
builtin/ui/clickable_elems.lua
Normal 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
|
|
@ -11,6 +11,7 @@ dofile(UI_PATH .. "selector.lua")
|
||||||
dofile(UI_PATH .. "style.lua")
|
dofile(UI_PATH .. "style.lua")
|
||||||
dofile(UI_PATH .. "elem.lua")
|
dofile(UI_PATH .. "elem.lua")
|
||||||
|
|
||||||
|
dofile(UI_PATH .. "clickable_elems.lua")
|
||||||
dofile(UI_PATH .. "static_elems.lua")
|
dofile(UI_PATH .. "static_elems.lua")
|
||||||
|
|
||||||
dofile(UI_PATH .. "window.lua")
|
dofile(UI_PATH .. "window.lua")
|
||||||
|
|
|
@ -246,6 +246,19 @@ func_preds["nth_last_match"] = function(str)
|
||||||
end
|
end
|
||||||
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)
|
local function parse_term(str, pred)
|
||||||
str = str:trim()
|
str = str:trim()
|
||||||
assert(str ~= "", "Expected selector term")
|
assert(str ~= "", "Expected selector term")
|
||||||
|
|
|
@ -19,6 +19,10 @@ local prelude_theme = ui.Style {
|
||||||
ui.Style "image" {
|
ui.Style "image" {
|
||||||
icon_scale = 0,
|
icon_scale = 0,
|
||||||
},
|
},
|
||||||
|
ui.Style "check, switch, radio" {
|
||||||
|
icon_place = "left",
|
||||||
|
text_align = "left",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function ui.get_prelude_theme()
|
function ui.get_prelude_theme()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
set(ui_SRCS
|
set(ui_SRCS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/box.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/box.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/clickable_elems.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/elem.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/elem.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/manager.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/manager.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/static_elems.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/static_elems.cpp
|
||||||
|
|
173
src/ui/clickable_elems.cpp
Normal file
173
src/ui/clickable_elems.cpp
Normal 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
102
src/ui/clickable_elems.h
Normal 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);
|
||||||
|
};
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
#include "util/serialize.h"
|
#include "util/serialize.h"
|
||||||
|
|
||||||
// Include every element header for Elem::create()
|
// Include every element header for Elem::create()
|
||||||
|
#include "ui/clickable_elems.h"
|
||||||
#include "ui/static_elems.h"
|
#include "ui/static_elems.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
@ -29,6 +30,9 @@ namespace ui
|
||||||
switch (type) {
|
switch (type) {
|
||||||
CREATE(ELEM, Elem);
|
CREATE(ELEM, Elem);
|
||||||
CREATE(ROOT, Root);
|
CREATE(ROOT, Root);
|
||||||
|
CREATE(BUTTON, Button);
|
||||||
|
CREATE(TOGGLE, Toggle);
|
||||||
|
CREATE(OPTION, Option);
|
||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,9 @@ namespace ui
|
||||||
{
|
{
|
||||||
ELEM = 0x00,
|
ELEM = 0x00,
|
||||||
ROOT = 0x01,
|
ROOT = 0x01,
|
||||||
|
BUTTON = 0x02,
|
||||||
|
TOGGLE = 0x03,
|
||||||
|
OPTION = 0x04,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The main box is always the zeroth item in the Box::NO_GROUP group.
|
// The main box is always the zeroth item in the Box::NO_GROUP group.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue