189 lines
7.5 KiB
Text
189 lines
7.5 KiB
Text
|
|
/*
|
||
|
|
Crafter®.Graphics
|
||
|
|
Copyright (C) 2026 Catcrafts®
|
||
|
|
catcrafts.net
|
||
|
|
|
||
|
|
This library is free software; you can redistribute it and/or
|
||
|
|
modify it under the terms of the GNU Lesser General Public
|
||
|
|
License version 3.0 as published by the Free Software Foundation;
|
||
|
|
|
||
|
|
This library is distributed in the hope that it will be useful,
|
||
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
|
Lesser General Public License for more details.
|
||
|
|
|
||
|
|
You should have received a copy of the GNU Lesser General Public
|
||
|
|
License along with this library; if not, write to the Free Software
|
||
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
|
*/
|
||
|
|
export module Crafter.Graphics:UIWidget;
|
||
|
|
import std;
|
||
|
|
import :UILength;
|
||
|
|
import :Types; // for CrafterKeys
|
||
|
|
|
||
|
|
export namespace Crafter::UI {
|
||
|
|
struct DrawList; // forward decl (full def in :UIDrawList)
|
||
|
|
|
||
|
|
// Threaded through layout. Holds anything every widget needs from the
|
||
|
|
// surrounding scene at layout time (DPI scale, root surface size, …).
|
||
|
|
struct LayoutContext {
|
||
|
|
float scale = 1.0f; // device scale (Window::scale)
|
||
|
|
Size surfaceSize{}; // root surface in device px
|
||
|
|
};
|
||
|
|
|
||
|
|
struct Widget {
|
||
|
|
Length width_ = Length::Auto();
|
||
|
|
Length height_ = Length::Auto();
|
||
|
|
Edges padding_;
|
||
|
|
Edges margin_;
|
||
|
|
std::optional<Anchor> anchor_;
|
||
|
|
|
||
|
|
// Layout output, filled by the engine.
|
||
|
|
Rect computedRect{};
|
||
|
|
Size desiredSize{};
|
||
|
|
bool dirty = true;
|
||
|
|
|
||
|
|
// Tree.
|
||
|
|
Widget* parent = nullptr;
|
||
|
|
std::vector<std::unique_ptr<Widget>> children_;
|
||
|
|
|
||
|
|
Widget() = default;
|
||
|
|
Widget(const Widget&) = delete;
|
||
|
|
Widget& operator=(const Widget&) = delete;
|
||
|
|
Widget(Widget&&) = default;
|
||
|
|
Widget& operator=(Widget&&) = default;
|
||
|
|
virtual ~Widget() = default;
|
||
|
|
|
||
|
|
// Layout protocol — Measure returns the size this widget wants given
|
||
|
|
// the available space; engine then calls Arrange with the final rect.
|
||
|
|
virtual Size Measure(Size avail, const LayoutContext& ctx) = 0;
|
||
|
|
virtual void Arrange(Rect rect, const LayoutContext& ctx) = 0;
|
||
|
|
|
||
|
|
// Interaction protocol — return true if the event was handled and
|
||
|
|
// should NOT bubble to the parent. Default: not handled.
|
||
|
|
virtual bool OnMouseClick(float /*x*/, float /*y*/) { return false; }
|
||
|
|
|
||
|
|
// Focus protocol. Widgets that opt in (e.g. InputField) return
|
||
|
|
// true from IsFocusable; UIScene tracks the currently-focused
|
||
|
|
// widget and routes keyboard events to it.
|
||
|
|
virtual bool IsFocusable() const { return false; }
|
||
|
|
virtual void OnFocus() {}
|
||
|
|
virtual void OnBlur() {}
|
||
|
|
|
||
|
|
// Keyboard input. Both default to "not handled". OnTextInput
|
||
|
|
// receives a UTF-8 substring (typically one codepoint per call).
|
||
|
|
// OnKeyDown receives non-character keys (Backspace, arrows, …).
|
||
|
|
virtual bool OnTextInput(std::string_view /*text*/) { return false; }
|
||
|
|
virtual bool OnKeyDown (CrafterKeys /*key*/) { return false; }
|
||
|
|
|
||
|
|
// Drawing protocol — emit GPU-bound draw items into `dl`. Default
|
||
|
|
// implementation is "container behaviour": just descend into
|
||
|
|
// children. Leaf widgets override to emit their own primitives;
|
||
|
|
// containers that also draw (Button background, ScrollView clip
|
||
|
|
// push/pop, TabView bar) override and explicitly recurse into
|
||
|
|
// children where appropriate.
|
||
|
|
//
|
||
|
|
// The body just forwards to children, so the forward-declared
|
||
|
|
// DrawList is enough — no member access here.
|
||
|
|
virtual void Emit(DrawList& dl) const {
|
||
|
|
for (auto& c : children_) c->Emit(dl);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Walk all descendants in pre-order.
|
||
|
|
template<typename F>
|
||
|
|
void ForEach(F&& f) {
|
||
|
|
f(*this);
|
||
|
|
for (auto& c : children_) c->ForEach(f);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// CRTP base providing fluent setters that return the concrete widget type.
|
||
|
|
template<typename Self>
|
||
|
|
struct WidgetBuilder : Widget {
|
||
|
|
Self& self() { return static_cast<Self&>(*this); }
|
||
|
|
|
||
|
|
Self& width(Length l) { width_ = l; return self(); }
|
||
|
|
Self& height(Length l) { height_ = l; return self(); }
|
||
|
|
Self& size(Length w, Length h) { width_ = w; height_ = h; return self(); }
|
||
|
|
Self& padding(Edges e) { padding_ = e; return self(); }
|
||
|
|
Self& padding(float all) { padding_ = Edges(all); return self(); }
|
||
|
|
Self& padding(float v, float h) { padding_ = Edges(v, h); return self(); }
|
||
|
|
Self& margin(Edges e) { margin_ = e; return self(); }
|
||
|
|
Self& margin(float all) { margin_ = Edges(all); return self(); }
|
||
|
|
Self& anchor(Anchor a) { anchor_ = a; return self(); }
|
||
|
|
Self& expand() { width_ = Length::Frac(1); height_ = Length::Frac(1); return self(); }
|
||
|
|
|
||
|
|
// Take ownership of a parameter pack of widgets and append them as children.
|
||
|
|
template<typename... Ws>
|
||
|
|
requires (std::derived_from<std::decay_t<Ws>, Widget> && ...)
|
||
|
|
Self& children(Ws&&... ws) {
|
||
|
|
children_.reserve(children_.size() + sizeof...(Ws));
|
||
|
|
(AppendChild(std::forward<Ws>(ws)), ...);
|
||
|
|
return self();
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
// .children(...) takes ownership of each widget argument unconditionally;
|
||
|
|
// builder chains like `Button{"X"}.font(f)` return Self& (lvalue ref to
|
||
|
|
// the temporary), so we always move rather than std::forward.
|
||
|
|
template<typename W>
|
||
|
|
void AppendChild(W&& w) {
|
||
|
|
using T = std::remove_cvref_t<W>;
|
||
|
|
auto p = std::make_unique<T>(std::move(w));
|
||
|
|
p->parent = this;
|
||
|
|
children_.push_back(std::move(p));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Stable typed handle into the scene; populated by the scene when a
|
||
|
|
// widget tree is mounted.
|
||
|
|
template<typename T>
|
||
|
|
struct WidgetRef {
|
||
|
|
T* node = nullptr;
|
||
|
|
|
||
|
|
T* operator->() const { return node; }
|
||
|
|
T& operator*() const { return *node; }
|
||
|
|
explicit operator bool() const { return node != nullptr; }
|
||
|
|
};
|
||
|
|
|
||
|
|
// Mutable observable value. Setting a new value invokes any registered
|
||
|
|
// watchers; widgets register watchers in their mount step to mark
|
||
|
|
// themselves dirty when the underlying value changes.
|
||
|
|
template<typename T>
|
||
|
|
class Observable {
|
||
|
|
public:
|
||
|
|
Observable() = default;
|
||
|
|
Observable(T v) : value_(std::move(v)) {}
|
||
|
|
|
||
|
|
Observable(const Observable&) = delete;
|
||
|
|
Observable& operator=(const Observable&) = delete;
|
||
|
|
|
||
|
|
Observable& operator=(T v) {
|
||
|
|
if constexpr (std::equality_comparable<T>) {
|
||
|
|
if (value_ == v) return *this;
|
||
|
|
}
|
||
|
|
value_ = std::move(v);
|
||
|
|
Notify();
|
||
|
|
return *this;
|
||
|
|
}
|
||
|
|
|
||
|
|
const T& Get() const { return value_; }
|
||
|
|
operator const T&() const { return value_; }
|
||
|
|
|
||
|
|
// Register a watcher; returned token unregisters on destruction.
|
||
|
|
// For V1 there is no unsubscribe — watchers live as long as the
|
||
|
|
// Observable does. The scene clears watchers when widgets are torn
|
||
|
|
// down by destroying the Observable they were watching.
|
||
|
|
void Watch(std::function<void()> fn) {
|
||
|
|
watchers_.push_back(std::move(fn));
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
T value_{};
|
||
|
|
std::vector<std::function<void()>> watchers_;
|
||
|
|
|
||
|
|
void Notify() {
|
||
|
|
for (auto& w : watchers_) w();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|