/* 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_; // Layout output, filled by the engine. Rect computedRect{}; Size desiredSize{}; bool dirty = true; // Tree. Widget* parent = nullptr; std::vector> 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 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 struct WidgetBuilder : Widget { Self& self() { return static_cast(*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 requires (std::derived_from, Widget> && ...) Self& children(Ws&&... ws) { children_.reserve(children_.size() + sizeof...(Ws)); (AppendChild(std::forward(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 void AppendChild(W&& w) { using T = std::remove_cvref_t; auto p = std::make_unique(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 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 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) { 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 fn) { watchers_.push_back(std::move(fn)); } private: T value_{}; std::vector> watchers_; void Notify() { for (auto& w : watchers_) w(); } }; }