new UI system
This commit is contained in:
parent
d840a81448
commit
216972e73a
82 changed files with 4837 additions and 3243 deletions
189
interfaces/Crafter.Graphics-UIWidget.cppm
Normal file
189
interfaces/Crafter.Graphics-UIWidget.cppm
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
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();
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue