+8
-11
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <luma/detail/errors.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
auto invalid(std::string message) -> std::expected<void, modulus::error> {
|
||||
@@ -26,7 +27,7 @@ auto internal(std::string message) -> std::expected<void, modulus::error> {
|
||||
return luma::detail::fail<void>(luma::detail::error_internal, std::move(message));
|
||||
}
|
||||
|
||||
auto validate(luma::animation const& animation) -> std::expected<void, modulus::error> {
|
||||
auto validate(luma::animation const &animation) -> std::expected<void, modulus::error> {
|
||||
if (!std::isfinite(animation.from)) {
|
||||
return invalid("animation from value must be finite");
|
||||
}
|
||||
@@ -42,11 +43,11 @@ auto validate(luma::animation const& animation) -> std::expected<void, modulus::
|
||||
return {};
|
||||
}
|
||||
|
||||
auto callback_error(std::char_t const* action, std::exception const& error) -> std::expected<void, modulus::error> {
|
||||
auto callback_error(std::char_t const *action, std::exception const &error) -> std::expected<void, modulus::error> {
|
||||
return internal(std::string{"animation "} + action + " callback failed: " + error.what());
|
||||
}
|
||||
|
||||
auto callback_error(std::char_t const* action) -> std::expected<void, modulus::error> {
|
||||
auto callback_error(std::char_t const *action) -> std::expected<void, modulus::error> {
|
||||
return internal(std::string{"animation "} + action + " callback failed with an unknown exception");
|
||||
}
|
||||
|
||||
@@ -104,12 +105,10 @@ auto luma::animation_graph::tick(std::chrono::steady_clock::duration delta) -> s
|
||||
if (ticking_) {
|
||||
return internal("animation graph is already ticking");
|
||||
}
|
||||
|
||||
auto active = std::move(animations_);
|
||||
animations_.clear();
|
||||
ticking_ = true;
|
||||
clear_requested_ = false;
|
||||
|
||||
auto fail_tick = [this](std::expected<void, modulus::error> status) {
|
||||
ticking_ = false;
|
||||
clear_requested_ = false;
|
||||
@@ -117,8 +116,7 @@ auto luma::animation_graph::tick(std::chrono::steady_clock::duration delta) -> s
|
||||
pending_.clear();
|
||||
return status;
|
||||
};
|
||||
|
||||
for (auto& running : active) {
|
||||
for (auto &running : active) {
|
||||
running.elapsed += delta;
|
||||
auto duration = std::chrono::duration_cast<std::chrono::steady_clock::duration>(running.animation.duration);
|
||||
if (duration.count() <= 0) {
|
||||
@@ -135,7 +133,7 @@ auto luma::animation_graph::tick(std::chrono::steady_clock::duration delta) -> s
|
||||
}
|
||||
try {
|
||||
running.animation.apply(*interpolated);
|
||||
} catch (std::exception const& error) {
|
||||
} catch (std::exception const &error) {
|
||||
return fail_tick(callback_error("apply", error));
|
||||
} catch (...) {
|
||||
return fail_tick(callback_error("apply"));
|
||||
@@ -147,7 +145,7 @@ auto luma::animation_graph::tick(std::chrono::steady_clock::duration delta) -> s
|
||||
if (running.animation.finished) {
|
||||
try {
|
||||
running.animation.finished();
|
||||
} catch (std::exception const& error) {
|
||||
} catch (std::exception const &error) {
|
||||
return fail_tick(callback_error("finished", error));
|
||||
} catch (...) {
|
||||
return fail_tick(callback_error("finished"));
|
||||
@@ -160,9 +158,8 @@ auto luma::animation_graph::tick(std::chrono::steady_clock::duration delta) -> s
|
||||
}
|
||||
animations_.push_back(std::move(running));
|
||||
}
|
||||
|
||||
ticking_ = false;
|
||||
for (auto& operation : pending_) {
|
||||
for (auto &operation : pending_) {
|
||||
if (operation.kind == luma::animation_graph::pending_kind::CLEAR) {
|
||||
animations_.clear();
|
||||
} else {
|
||||
|
||||
+97
-101
@@ -38,6 +38,7 @@
|
||||
#include <luma/render.hpp>
|
||||
#include <luma/routing.hpp>
|
||||
|
||||
|
||||
namespace luma::detail {
|
||||
|
||||
struct application_core {
|
||||
@@ -59,7 +60,7 @@ struct application_core {
|
||||
luma::window_manager windows{};
|
||||
std::unique_ptr<luma::asset_cache> assets{};
|
||||
std::unique_ptr<luma::renderer_resources> resources{};
|
||||
luma::overlay_host* overlays{};
|
||||
luma::overlay_host *overlays{};
|
||||
std::atomic_bool layout_dirty{true};
|
||||
std::atomic_bool redraw_dirty{true};
|
||||
std::bool_t layout_ready{false};
|
||||
@@ -75,12 +76,11 @@ namespace {
|
||||
struct font_key {
|
||||
std::string path{};
|
||||
std::int32_t size{};
|
||||
|
||||
auto operator==(font_key const& other) const -> std::bool_t;
|
||||
auto operator==(font_key const &other) const -> std::bool_t;
|
||||
};
|
||||
|
||||
struct font_key_hash {
|
||||
auto operator()(font_key const& key) const noexcept -> std::size_t;
|
||||
auto operator()(font_key const &key) const noexcept -> std::size_t;
|
||||
};
|
||||
|
||||
struct raylib_resource_state {
|
||||
@@ -111,7 +111,7 @@ private:
|
||||
class raylib_resources final : public luma::renderer_resources {
|
||||
public:
|
||||
explicit raylib_resources(std::shared_ptr<raylib_resource_state> state);
|
||||
auto load_texture(std::string const& path) -> std::expected<std::shared_ptr<luma::texture>, modulus::error> override;
|
||||
auto load_texture(std::string const &path) -> std::expected<std::shared_ptr<luma::texture>, modulus::error> override;
|
||||
auto state() const -> std::shared_ptr<raylib_resource_state>;
|
||||
private:
|
||||
std::shared_ptr<raylib_resource_state> state_{};
|
||||
@@ -125,11 +125,11 @@ public:
|
||||
auto stroke_line(luma::point from, luma::point to, std::float32_t thickness, luma::color color) -> void override;
|
||||
auto fill_ellipse(luma::rect bounds, luma::color color) -> void override;
|
||||
auto stroke_ellipse(luma::rect bounds, std::float32_t thickness, luma::color color) -> void override;
|
||||
auto draw_text(std::string_view text, luma::point origin, luma::text_style const& style, luma::color color) -> void override;
|
||||
auto draw_texture(luma::texture const& texture, luma::rect source, luma::rect destination, luma::color tint) -> void override;
|
||||
auto draw_text(std::string_view text, luma::point origin, luma::text_style const &style, luma::color color) -> void override;
|
||||
auto draw_texture(luma::texture const &texture, luma::rect source, luma::rect destination, luma::color tint) -> void override;
|
||||
auto push_clip(luma::rect bounds) -> void override;
|
||||
auto pop_clip() -> void override;
|
||||
auto measure_text(std::string_view text, luma::text_style const& style) const -> luma::size override;
|
||||
auto measure_text(std::string_view text, luma::text_style const &style) const -> luma::size override;
|
||||
private:
|
||||
std::shared_ptr<raylib_resource_state> state_{};
|
||||
std::vector<luma::rect> clip_stack_{};
|
||||
@@ -138,8 +138,8 @@ private:
|
||||
class scope_exit {
|
||||
public:
|
||||
explicit scope_exit(std::function<void()> action);
|
||||
scope_exit(scope_exit const&) = delete;
|
||||
auto operator=(scope_exit const&) -> scope_exit& = delete;
|
||||
scope_exit(scope_exit const &) = delete;
|
||||
auto operator=(scope_exit const &) -> scope_exit & = delete;
|
||||
~scope_exit();
|
||||
private:
|
||||
std::function<void()> action_{};
|
||||
@@ -149,8 +149,8 @@ private:
|
||||
class drawing_scope {
|
||||
public:
|
||||
drawing_scope();
|
||||
drawing_scope(drawing_scope const&) = delete;
|
||||
auto operator=(drawing_scope const&) -> drawing_scope& = delete;
|
||||
drawing_scope(drawing_scope const &) = delete;
|
||||
auto operator=(drawing_scope const &) -> drawing_scope & = delete;
|
||||
~drawing_scope();
|
||||
auto end() -> void;
|
||||
private:
|
||||
@@ -159,12 +159,12 @@ private:
|
||||
|
||||
class ui_traversal_scope {
|
||||
public:
|
||||
explicit ui_traversal_scope(luma::detail::application_core& core);
|
||||
ui_traversal_scope(ui_traversal_scope const&) = delete;
|
||||
auto operator=(ui_traversal_scope const&) -> ui_traversal_scope& = delete;
|
||||
explicit ui_traversal_scope(luma::detail::application_core &core);
|
||||
ui_traversal_scope(ui_traversal_scope const &) = delete;
|
||||
auto operator=(ui_traversal_scope const &) -> ui_traversal_scope & = delete;
|
||||
~ui_traversal_scope();
|
||||
private:
|
||||
luma::detail::application_core& core_;
|
||||
luma::detail::application_core &core_;
|
||||
};
|
||||
|
||||
constexpr auto max_layout_passes = std::uint32_t{8};
|
||||
@@ -200,25 +200,25 @@ auto same_rect(luma::rect left, luma::rect right) -> std::bool_t {
|
||||
return left.x == right.x && left.y == right.y && left.width == right.width && left.height == right.height;
|
||||
}
|
||||
|
||||
auto make_event(luma::touch_event const& event, luma::touch_point point) -> luma::touch_event {
|
||||
auto make_event(luma::touch_event const &event, luma::touch_point point) -> luma::touch_event {
|
||||
return {
|
||||
.points = {point},
|
||||
.time = event.time,
|
||||
};
|
||||
}
|
||||
|
||||
auto invalidate_layout_cache(luma::detail::application_core& core) -> void;
|
||||
auto invalidate_layout_cache(luma::detail::application_core &core) -> void;
|
||||
|
||||
auto mark_redraw_dirty(luma::detail::application_core& core) -> void {
|
||||
auto mark_redraw_dirty(luma::detail::application_core &core) -> void {
|
||||
core.redraw_dirty.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
auto mark_layout_dirty(luma::detail::application_core& core) -> void {
|
||||
auto mark_layout_dirty(luma::detail::application_core &core) -> void {
|
||||
core.layout_dirty.store(true, std::memory_order_release);
|
||||
mark_redraw_dirty(core);
|
||||
}
|
||||
|
||||
auto make_layout_invalidator(std::shared_ptr<luma::detail::application_core> const& core) -> std::function<void()> {
|
||||
auto make_layout_invalidator(std::shared_ptr<luma::detail::application_core> const &core) -> std::function<void()> {
|
||||
auto weak = std::weak_ptr<luma::detail::application_core>{core};
|
||||
return [weak] {
|
||||
if (auto locked = weak.lock()) {
|
||||
@@ -227,17 +227,17 @@ auto make_layout_invalidator(std::shared_ptr<luma::detail::application_core> con
|
||||
};
|
||||
}
|
||||
|
||||
auto bind_layout_invalidator(std::shared_ptr<luma::detail::application_core> const& core, luma::control& control) -> void {
|
||||
auto bind_layout_invalidator(std::shared_ptr<luma::detail::application_core> const &core, luma::control &control) -> void {
|
||||
control.bind_layout_invalidator(make_layout_invalidator(core));
|
||||
}
|
||||
|
||||
auto flush_retired_controls(luma::detail::application_core& core) -> void {
|
||||
auto flush_retired_controls(luma::detail::application_core &core) -> void {
|
||||
if (core.traversal_depth == 0) {
|
||||
core.retired_controls.clear();
|
||||
}
|
||||
}
|
||||
|
||||
auto retire_control(luma::detail::application_core& core, std::unique_ptr<luma::control> control) -> void {
|
||||
auto retire_control(luma::detail::application_core &core, std::unique_ptr<luma::control> control) -> void {
|
||||
if (control == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -248,7 +248,7 @@ auto retire_control(luma::detail::application_core& core, std::unique_ptr<luma::
|
||||
core.retired_controls.push_back(std::move(control));
|
||||
}
|
||||
|
||||
auto install_retire_handlers(std::shared_ptr<luma::detail::application_core> const& core) -> void {
|
||||
auto install_retire_handlers(std::shared_ptr<luma::detail::application_core> const &core) -> void {
|
||||
auto weak = std::weak_ptr<luma::detail::application_core>{core};
|
||||
core->router.set_retire_handler([weak](std::unique_ptr<luma::control> control) mutable {
|
||||
if (auto locked = weak.lock()) {
|
||||
@@ -257,27 +257,27 @@ auto install_retire_handlers(std::shared_ptr<luma::detail::application_core> con
|
||||
});
|
||||
}
|
||||
|
||||
auto active_control(luma::detail::application_core& core) -> luma::control* {
|
||||
auto* active = core.root.get();
|
||||
auto active_control(luma::detail::application_core &core) -> luma::control * {
|
||||
auto *active = core.root.get();
|
||||
if (active == nullptr) {
|
||||
active = core.router.current();
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
auto invalidate_layout_cache(luma::detail::application_core& core) -> void {
|
||||
auto invalidate_layout_cache(luma::detail::application_core &core) -> void {
|
||||
auto traversal = ui_traversal_scope{core};
|
||||
if (auto* active = active_control(core); active != nullptr) {
|
||||
if (auto *active = active_control(core); active != nullptr) {
|
||||
active->invalidate_layout();
|
||||
}
|
||||
core.windows.invalidate_layout();
|
||||
}
|
||||
|
||||
template <typename VALUE, typename TASK>
|
||||
auto run_task(TASK& task) -> std::expected<VALUE, modulus::error> {
|
||||
auto run_task(TASK &task) -> std::expected<VALUE, modulus::error> {
|
||||
try {
|
||||
return task();
|
||||
} catch (std::exception const& error) {
|
||||
} catch (std::exception const &error) {
|
||||
return internal<VALUE>(error.what());
|
||||
} catch (...) {
|
||||
return internal<VALUE>("UI task failed with an unknown exception");
|
||||
@@ -285,7 +285,7 @@ auto run_task(TASK& task) -> std::expected<VALUE, modulus::error> {
|
||||
}
|
||||
|
||||
template <typename VALUE, typename TASK>
|
||||
auto run_ui_task(luma::detail::application_core& core, TASK& task, std::bool_t dirty_after) -> std::expected<VALUE, modulus::error> {
|
||||
auto run_ui_task(luma::detail::application_core &core, TASK &task, std::bool_t dirty_after) -> std::expected<VALUE, modulus::error> {
|
||||
auto result = run_task<VALUE>(task);
|
||||
if (dirty_after) {
|
||||
mark_layout_dirty(core);
|
||||
@@ -294,15 +294,13 @@ auto run_ui_task(luma::detail::application_core& core, TASK& task, std::bool_t d
|
||||
}
|
||||
|
||||
template <typename VALUE, typename TASK>
|
||||
auto dispatch_to_ui(std::shared_ptr<luma::detail::application_core> const& core, TASK&& input, std::bool_t dirty_after = true) -> std::expected<VALUE, modulus::error> {
|
||||
auto dispatch_to_ui(std::shared_ptr<luma::detail::application_core> const &core, TASK &&input, std::bool_t dirty_after = true) -> std::expected<VALUE, modulus::error> {
|
||||
if (!core) {
|
||||
return unavailable<VALUE>("application is not initialized");
|
||||
}
|
||||
|
||||
auto task = std::decay_t<TASK>{std::forward<TASK>(input)};
|
||||
auto should_post = false;
|
||||
auto wait_for_startup = false;
|
||||
|
||||
{
|
||||
auto lock = std::unique_lock{core->lifecycle_mutex};
|
||||
if (core->stopping.load(std::memory_order_acquire)) {
|
||||
@@ -317,7 +315,6 @@ auto dispatch_to_ui(std::shared_ptr<luma::detail::application_core> const& core,
|
||||
should_post = running && accepting;
|
||||
wait_for_startup = running && !accepting;
|
||||
}
|
||||
|
||||
if (!should_post || wait_for_startup) {
|
||||
auto immediate_lock = std::scoped_lock{core->immediate_mutex};
|
||||
auto lock = std::unique_lock{core->lifecycle_mutex};
|
||||
@@ -337,7 +334,6 @@ auto dispatch_to_ui(std::shared_ptr<luma::detail::application_core> const& core,
|
||||
}
|
||||
should_post = true;
|
||||
}
|
||||
|
||||
auto future = std::future<std::expected<VALUE, modulus::error>>{};
|
||||
auto promise = std::make_shared<std::promise<std::expected<VALUE, modulus::error>>>();
|
||||
future = promise->get_future();
|
||||
@@ -359,21 +355,20 @@ auto dispatch_to_ui(std::shared_ptr<luma::detail::application_core> const& core,
|
||||
}
|
||||
promise->set_value(run_ui_task<VALUE>(*core, queued, dirty_after));
|
||||
});
|
||||
} catch (std::exception const& error) {
|
||||
} catch (std::exception const &error) {
|
||||
return internal<VALUE>(error.what());
|
||||
} catch (...) {
|
||||
return internal<VALUE>("failed to queue UI task");
|
||||
}
|
||||
|
||||
return future.get();
|
||||
}
|
||||
|
||||
auto drain_ui_tasks(luma::detail::application_core& core) -> std::size_t {
|
||||
auto drain_ui_tasks(luma::detail::application_core &core) -> std::size_t {
|
||||
core.ui_context.restart();
|
||||
return core.ui_context.poll();
|
||||
}
|
||||
|
||||
auto finish_run(luma::detail::application_core& core) -> void {
|
||||
auto finish_run(luma::detail::application_core &core) -> void {
|
||||
auto lock = std::scoped_lock{core.lifecycle_mutex};
|
||||
core.accepting_tasks.store(false, std::memory_order_release);
|
||||
core.running.store(false, std::memory_order_release);
|
||||
@@ -382,7 +377,7 @@ auto finish_run(luma::detail::application_core& core) -> void {
|
||||
core.stop_requested.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
auto cleanup_ui_state(luma::detail::application_core& core) -> void {
|
||||
auto cleanup_ui_state(luma::detail::application_core &core) -> void {
|
||||
if (core.assets) {
|
||||
core.assets->clear();
|
||||
}
|
||||
@@ -398,7 +393,7 @@ auto cleanup_ui_state(luma::detail::application_core& core) -> void {
|
||||
mark_layout_dirty(core);
|
||||
}
|
||||
|
||||
auto unload_record(raylib_resource_state::texture_record& record) -> void {
|
||||
auto unload_record(raylib_resource_state::texture_record &record) -> void {
|
||||
if (!record.unloaded && record.texture.id != 0) {
|
||||
UnloadTexture(record.texture);
|
||||
record.unloaded = true;
|
||||
@@ -406,7 +401,7 @@ auto unload_record(raylib_resource_state::texture_record& record) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto flush_pending_unloads(raylib_resource_state& state) -> std::bool_t {
|
||||
auto flush_pending_unloads(raylib_resource_state &state) -> std::bool_t {
|
||||
assert(state.ui_thread == std::this_thread::get_id());
|
||||
auto pending = std::vector<std::shared_ptr<raylib_resource_state::texture_record>>{};
|
||||
{
|
||||
@@ -414,7 +409,7 @@ auto flush_pending_unloads(raylib_resource_state& state) -> std::bool_t {
|
||||
pending.swap(state.pending_unloads);
|
||||
}
|
||||
auto unloaded = false;
|
||||
for (auto& record : pending) {
|
||||
for (auto &record : pending) {
|
||||
if (record) {
|
||||
unload_record(*record);
|
||||
unloaded = true;
|
||||
@@ -423,7 +418,7 @@ auto flush_pending_unloads(raylib_resource_state& state) -> std::bool_t {
|
||||
return unloaded;
|
||||
}
|
||||
|
||||
auto unload_all_textures(raylib_resource_state& state) -> void {
|
||||
auto unload_all_textures(raylib_resource_state &state) -> void {
|
||||
assert(state.ui_thread == std::this_thread::get_id());
|
||||
static_cast<void>(flush_pending_unloads(state));
|
||||
auto records = std::vector<std::shared_ptr<raylib_resource_state::texture_record>>{};
|
||||
@@ -439,14 +434,14 @@ auto unload_all_textures(raylib_resource_state& state) -> void {
|
||||
}
|
||||
state.records.erase(write, state.records.end());
|
||||
}
|
||||
for (auto& record : records) {
|
||||
for (auto &record : records) {
|
||||
unload_record(*record);
|
||||
}
|
||||
}
|
||||
|
||||
auto unload_all_fonts(raylib_resource_state& state) -> void {
|
||||
auto unload_all_fonts(raylib_resource_state &state) -> void {
|
||||
assert(state.ui_thread == std::this_thread::get_id());
|
||||
for (auto& [path, font] : state.fonts) {
|
||||
for (auto &[path, font] : state.fonts) {
|
||||
static_cast<void>(path);
|
||||
if (font.texture.id != 0) {
|
||||
UnloadFont(font);
|
||||
@@ -455,7 +450,7 @@ auto unload_all_fonts(raylib_resource_state& state) -> void {
|
||||
state.fonts.clear();
|
||||
}
|
||||
|
||||
auto resolve_font(raylib_resource_state& state, luma::text_style const& style) -> Font {
|
||||
auto resolve_font(raylib_resource_state &state, luma::text_style const &style) -> Font {
|
||||
assert(state.ui_thread == std::this_thread::get_id());
|
||||
assert(IsWindowReady());
|
||||
if (style.font.empty()) {
|
||||
@@ -496,7 +491,7 @@ auto begin_clip(luma::rect bounds) -> void {
|
||||
BeginScissorMode(static_cast<std::int32_t>(bounds.x), static_cast<std::int32_t>(bounds.y), static_cast<std::int32_t>(bounds.width), static_cast<std::int32_t>(bounds.height));
|
||||
}
|
||||
|
||||
auto poll_touch(std::unordered_map<std::int32_t, luma::point>& previous, std::int32_t max_points) -> luma::touch_event {
|
||||
auto poll_touch(std::unordered_map<std::int32_t, luma::point> &previous, std::int32_t max_points) -> luma::touch_event {
|
||||
assert(IsWindowReady());
|
||||
assert(max_points > 0);
|
||||
auto current = std::unordered_map<std::int32_t, luma::point>{};
|
||||
@@ -517,7 +512,7 @@ auto poll_touch(std::unordered_map<std::int32_t, luma::point>& previous, std::in
|
||||
.phase = phase,
|
||||
});
|
||||
}
|
||||
for (auto const& [id, position] : previous) {
|
||||
for (auto const &[id, position] : previous) {
|
||||
if (!current.contains(id)) {
|
||||
points.push_back({
|
||||
.id = id,
|
||||
@@ -533,17 +528,18 @@ auto poll_touch(std::unordered_map<std::int32_t, luma::point>& previous, std::in
|
||||
};
|
||||
}
|
||||
|
||||
auto font_key::operator==(font_key const& other) const -> std::bool_t {
|
||||
auto font_key::operator==(font_key const &other) const -> std::bool_t {
|
||||
return path == other.path && size == other.size;
|
||||
}
|
||||
|
||||
auto font_key_hash::operator()(font_key const& key) const noexcept -> std::size_t {
|
||||
auto font_key_hash::operator()(font_key const &key) const noexcept -> std::size_t {
|
||||
auto seed = std::hash<std::string>{}(key.path);
|
||||
seed ^= std::hash<std::int32_t>{}(key.size) + 0x9e3779b9U + (seed << 6U) + (seed >> 2U);
|
||||
return seed;
|
||||
}
|
||||
|
||||
scope_exit::scope_exit(std::function<void()> action) : action_{std::move(action)} {}
|
||||
scope_exit::scope_exit(std::function<void()> action)
|
||||
: action_{std::move(action)} {}
|
||||
|
||||
scope_exit::~scope_exit() {
|
||||
if (!active_ || !action_) {
|
||||
@@ -576,7 +572,8 @@ auto drawing_scope::end() -> void {
|
||||
}
|
||||
}
|
||||
|
||||
ui_traversal_scope::ui_traversal_scope(luma::detail::application_core& core) : core_{core} {
|
||||
ui_traversal_scope::ui_traversal_scope(luma::detail::application_core &core)
|
||||
: core_{core} {
|
||||
++core_.traversal_depth;
|
||||
}
|
||||
|
||||
@@ -586,7 +583,8 @@ ui_traversal_scope::~ui_traversal_scope() {
|
||||
flush_retired_controls(core_);
|
||||
}
|
||||
|
||||
raylib_texture::raylib_texture(std::shared_ptr<raylib_resource_state::texture_record> record, std::shared_ptr<raylib_resource_state> state) : record_{std::move(record)}, state_{std::move(state)} {
|
||||
raylib_texture::raylib_texture(std::shared_ptr<raylib_resource_state::texture_record> record, std::shared_ptr<raylib_resource_state> state)
|
||||
: record_{std::move(record)}, state_{std::move(state)} {
|
||||
assert(record_ != nullptr);
|
||||
assert(record_->texture.id != 0);
|
||||
assert(record_->texture.width > 0);
|
||||
@@ -623,11 +621,12 @@ auto raylib_texture::native() const -> Texture2D {
|
||||
return record_->texture;
|
||||
}
|
||||
|
||||
raylib_resources::raylib_resources(std::shared_ptr<raylib_resource_state> state) : state_{std::move(state)} {
|
||||
raylib_resources::raylib_resources(std::shared_ptr<raylib_resource_state> state)
|
||||
: state_{std::move(state)} {
|
||||
assert(state_ != nullptr);
|
||||
}
|
||||
|
||||
auto raylib_resources::load_texture(std::string const& path) -> std::expected<std::shared_ptr<luma::texture>, modulus::error> {
|
||||
auto raylib_resources::load_texture(std::string const &path) -> std::expected<std::shared_ptr<luma::texture>, modulus::error> {
|
||||
assert(state_ != nullptr);
|
||||
assert(state_->active.load(std::memory_order_acquire));
|
||||
assert(state_->ui_thread == std::this_thread::get_id());
|
||||
@@ -652,29 +651,32 @@ auto raylib_resources::state() const -> std::shared_ptr<raylib_resource_state> {
|
||||
return state_;
|
||||
}
|
||||
|
||||
raylib_draw_context::raylib_draw_context(std::shared_ptr<raylib_resource_state> state) : state_{std::move(state)} {
|
||||
raylib_draw_context::raylib_draw_context(std::shared_ptr<raylib_resource_state> state)
|
||||
: state_{std::move(state)} {
|
||||
assert(state_ != nullptr);
|
||||
}
|
||||
|
||||
auto raylib_draw_context::fill_rect(luma::rect bounds, luma::color color) -> void {
|
||||
assert(IsWindowReady());
|
||||
DrawRectangleRec({
|
||||
.x = bounds.x,
|
||||
.y = bounds.y,
|
||||
.width = bounds.width,
|
||||
.height = bounds.height,
|
||||
}, to_raylib(color));
|
||||
.x = bounds.x,
|
||||
.y = bounds.y,
|
||||
.width = bounds.width,
|
||||
.height = bounds.height,
|
||||
},
|
||||
to_raylib(color));
|
||||
}
|
||||
|
||||
auto raylib_draw_context::stroke_rect(luma::rect bounds, std::float32_t thickness, luma::color color) -> void {
|
||||
assert(IsWindowReady());
|
||||
assert(thickness >= 0.0F);
|
||||
DrawRectangleLinesEx({
|
||||
.x = bounds.x,
|
||||
.y = bounds.y,
|
||||
.width = bounds.width,
|
||||
.height = bounds.height,
|
||||
}, thickness, to_raylib(color));
|
||||
.x = bounds.x,
|
||||
.y = bounds.y,
|
||||
.width = bounds.width,
|
||||
.height = bounds.height,
|
||||
},
|
||||
thickness, to_raylib(color));
|
||||
}
|
||||
|
||||
auto raylib_draw_context::stroke_line(luma::point from, luma::point to, std::float32_t thickness, luma::color color) -> void {
|
||||
@@ -719,21 +721,22 @@ auto raylib_draw_context::stroke_ellipse(luma::rect bounds, std::float32_t thick
|
||||
}
|
||||
}
|
||||
|
||||
auto raylib_draw_context::draw_text(std::string_view text, luma::point origin, luma::text_style const& style, luma::color color) -> void {
|
||||
auto raylib_draw_context::draw_text(std::string_view text, luma::point origin, luma::text_style const &style, luma::color color) -> void {
|
||||
assert(IsWindowReady());
|
||||
assert(state_ != nullptr);
|
||||
assert(style.size > 0.0F);
|
||||
auto owned = std::string{text};
|
||||
auto font = resolve_font(*state_, style);
|
||||
DrawTextEx(font, owned.c_str(), {
|
||||
.x = origin.x,
|
||||
.y = origin.y,
|
||||
}, style.size, 1.0F, to_raylib(color));
|
||||
.x = origin.x,
|
||||
.y = origin.y,
|
||||
},
|
||||
style.size, 1.0F, to_raylib(color));
|
||||
}
|
||||
|
||||
auto raylib_draw_context::draw_texture(luma::texture const& texture, luma::rect source, luma::rect destination, luma::color tint) -> void {
|
||||
auto raylib_draw_context::draw_texture(luma::texture const &texture, luma::rect source, luma::rect destination, luma::color tint) -> void {
|
||||
assert(IsWindowReady());
|
||||
auto const* native = dynamic_cast<raylib_texture const*>(&texture);
|
||||
auto const *native = dynamic_cast<raylib_texture const *>(&texture);
|
||||
if (native == nullptr || !native->valid()) {
|
||||
return;
|
||||
}
|
||||
@@ -776,7 +779,7 @@ auto raylib_draw_context::pop_clip() -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto raylib_draw_context::measure_text(std::string_view text, luma::text_style const& style) const -> luma::size {
|
||||
auto raylib_draw_context::measure_text(std::string_view text, luma::text_style const &style) const -> luma::size {
|
||||
assert(IsWindowReady());
|
||||
assert(state_ != nullptr);
|
||||
assert(style.size > 0.0F);
|
||||
@@ -791,15 +794,17 @@ auto raylib_draw_context::measure_text(std::string_view text, luma::text_style c
|
||||
|
||||
}
|
||||
|
||||
luma::detail::application_core::application_core(luma::application_config value) : ui_strand{boost::asio::make_strand(ui_context)}, config{std::move(value)} {}
|
||||
luma::detail::application_core::application_core(luma::application_config value)
|
||||
: ui_strand{boost::asio::make_strand(ui_context)}, config{std::move(value)} {}
|
||||
|
||||
luma::application::application(luma::application_config config) : core_{std::make_shared<luma::detail::application_core>(std::move(config))} {
|
||||
luma::application::application(luma::application_config config)
|
||||
: core_{std::make_shared<luma::detail::application_core>(std::move(config))} {
|
||||
assert(core_ != nullptr);
|
||||
install_retire_handlers(core_);
|
||||
core_->windows.bind_layout_invalidator(make_layout_invalidator(core_));
|
||||
}
|
||||
|
||||
luma::application::application(luma::application&& other) noexcept {
|
||||
luma::application::application(luma::application &&other) noexcept {
|
||||
auto lock = std::scoped_lock{other.core_mutex_};
|
||||
core_ = std::move(other.core_);
|
||||
}
|
||||
@@ -808,7 +813,7 @@ luma::application::~application() {
|
||||
request_stop();
|
||||
}
|
||||
|
||||
auto luma::application::operator=(luma::application&& other) noexcept -> luma::application& {
|
||||
auto luma::application::operator=(luma::application &&other) noexcept -> luma::application & {
|
||||
if (this != &other) {
|
||||
request_stop();
|
||||
auto lock = std::scoped_lock{
|
||||
@@ -851,7 +856,7 @@ auto luma::application::replace_route(std::string name) -> std::expected<void, m
|
||||
return dispatch_to_ui<void>(core, [core, name = std::move(name)] {
|
||||
auto status = core->router.replace(name);
|
||||
if (status) {
|
||||
if (auto* current = core->router.current(); current != nullptr) {
|
||||
if (auto *current = core->router.current(); current != nullptr) {
|
||||
bind_layout_invalidator(core, *current);
|
||||
}
|
||||
}
|
||||
@@ -865,7 +870,7 @@ auto luma::application::push_route(std::string name) -> std::expected<void, modu
|
||||
return dispatch_to_ui<void>(core, [core, name = std::move(name)] {
|
||||
auto status = core->router.push(name);
|
||||
if (status) {
|
||||
if (auto* current = core->router.current(); current != nullptr) {
|
||||
if (auto *current = core->router.current(); current != nullptr) {
|
||||
bind_layout_invalidator(core, *current);
|
||||
}
|
||||
}
|
||||
@@ -878,7 +883,7 @@ auto luma::application::pop_route() -> std::expected<void, modulus::error> {
|
||||
return dispatch_to_ui<void>(core, [core] {
|
||||
auto status = core->router.pop();
|
||||
if (status) {
|
||||
if (auto* current = core->router.current(); current != nullptr) {
|
||||
if (auto *current = core->router.current(); current != nullptr) {
|
||||
bind_layout_invalidator(core, *current);
|
||||
}
|
||||
}
|
||||
@@ -888,9 +893,7 @@ auto luma::application::pop_route() -> std::expected<void, modulus::error> {
|
||||
|
||||
auto luma::application::current_route() const -> std::expected<std::string, modulus::error> {
|
||||
auto core = core_snapshot();
|
||||
return dispatch_to_ui<std::string>(core, [core] {
|
||||
return modulus::success(std::string{core->router.current_route()});
|
||||
}, false);
|
||||
return dispatch_to_ui<std::string>(core, [core] { return modulus::success(std::string{core->router.current_route()}); }, false);
|
||||
}
|
||||
|
||||
auto luma::application::add_animation(luma::animation animation) -> std::expected<void, modulus::error> {
|
||||
@@ -910,9 +913,7 @@ auto luma::application::clear_animations() -> std::expected<void, modulus::error
|
||||
|
||||
auto luma::application::animations_empty() const -> std::expected<std::bool_t, modulus::error> {
|
||||
auto core = core_snapshot();
|
||||
return dispatch_to_ui<std::bool_t>(core, [core] {
|
||||
return modulus::success(core->animations.empty());
|
||||
}, false);
|
||||
return dispatch_to_ui<std::bool_t>(core, [core] { return modulus::success(core->animations.empty()); }, false);
|
||||
}
|
||||
|
||||
auto luma::application::open_window(luma::window_options options, std::unique_ptr<luma::control> content) -> std::expected<std::uint64_t, modulus::error> {
|
||||
@@ -981,8 +982,7 @@ auto luma::application::load_texture(std::string path) -> std::expected<std::sha
|
||||
if (!core->renderer_active.load(std::memory_order_acquire) || !core->assets) {
|
||||
return unavailable<std::shared_ptr<luma::texture>>("texture loading requires an active renderer");
|
||||
}
|
||||
return core->assets->texture(path);
|
||||
}, false);
|
||||
return core->assets->texture(path); }, false);
|
||||
}
|
||||
|
||||
auto luma::application::request_layout() -> std::expected<void, modulus::error> {
|
||||
@@ -1100,7 +1100,6 @@ auto luma::application::run() -> std::expected<void, modulus::error> {
|
||||
core->running.store(true, std::memory_order_release);
|
||||
core->accepting_tasks.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
auto resource_state = std::shared_ptr<raylib_resource_state>{};
|
||||
auto window_ready = false;
|
||||
[[maybe_unused]] auto cleanup = scope_exit{[&core, &resource_state, &window_ready] {
|
||||
@@ -1123,7 +1122,6 @@ auto luma::application::run() -> std::expected<void, modulus::error> {
|
||||
}
|
||||
finish_run(*core);
|
||||
}};
|
||||
|
||||
InitWindow(core->config.display.width, core->config.display.height, core->config.title.c_str());
|
||||
if (!IsWindowReady()) {
|
||||
return unavailable<void>("failed to initialize raylib window");
|
||||
@@ -1135,7 +1133,6 @@ auto luma::application::run() -> std::expected<void, modulus::error> {
|
||||
auto current_target_fps = core->config.display.target_fps;
|
||||
auto const idle_fps = core->config.display.idle_fps;
|
||||
SetTargetFPS(current_target_fps);
|
||||
|
||||
resource_state = std::make_shared<raylib_resource_state>();
|
||||
resource_state->ui_thread = std::this_thread::get_id();
|
||||
resource_state->active.store(true, std::memory_order_release);
|
||||
@@ -1147,7 +1144,6 @@ auto luma::application::run() -> std::expected<void, modulus::error> {
|
||||
core->accepting_tasks.store(true, std::memory_order_release);
|
||||
}
|
||||
startup_lock.unlock();
|
||||
|
||||
auto context = raylib_draw_context{resource_state};
|
||||
auto previous_touches = std::unordered_map<std::int32_t, luma::point>{};
|
||||
auto previous_frame = std::chrono::steady_clock::now();
|
||||
@@ -1181,13 +1177,13 @@ auto luma::application::run() -> std::expected<void, modulus::error> {
|
||||
}
|
||||
auto touch_requires_layout = false;
|
||||
if (!event.points.empty()) {
|
||||
for (auto const& point : event.points) {
|
||||
for (auto const &point : event.points) {
|
||||
auto single = make_event(event, point);
|
||||
if (core->windows.handle_touch(single)) {
|
||||
touch_requires_layout = touch_requires_layout || point.phase != luma::touch_phase::MOVE;
|
||||
continue;
|
||||
}
|
||||
auto* active = active_control(*core);
|
||||
auto *active = active_control(*core);
|
||||
if (active != nullptr && active->handle_touch(single)) {
|
||||
touch_requires_layout = touch_requires_layout || point.phase != luma::touch_phase::MOVE;
|
||||
}
|
||||
@@ -1223,7 +1219,7 @@ auto luma::application::run() -> std::expected<void, modulus::error> {
|
||||
if (pass > max_layout_passes) {
|
||||
return modulus::fail<void>(internal_error("application layout did not stabilize"));
|
||||
}
|
||||
auto* active = active_control(*core);
|
||||
auto *active = active_control(*core);
|
||||
if (active != nullptr) {
|
||||
active->measure(context, core->config.theme, limits);
|
||||
active->arrange(display);
|
||||
@@ -1244,7 +1240,7 @@ auto luma::application::run() -> std::expected<void, modulus::error> {
|
||||
if (should_draw) {
|
||||
auto drawing = drawing_scope{};
|
||||
ClearBackground(to_raylib(core->config.theme.background));
|
||||
auto* active = active_control(*core);
|
||||
auto *active = active_control(*core);
|
||||
if (active != nullptr) {
|
||||
active->draw(context, core->config.theme);
|
||||
}
|
||||
@@ -1255,7 +1251,7 @@ auto luma::application::run() -> std::expected<void, modulus::error> {
|
||||
PollInputEvents();
|
||||
}
|
||||
}
|
||||
} catch (std::exception const& error) {
|
||||
} catch (std::exception const &error) {
|
||||
return modulus::fail<void>(internal_error(std::string{"application frame failed: "} + error.what()));
|
||||
} catch (...) {
|
||||
return modulus::fail<void>(internal_error("application frame failed with an unknown exception"));
|
||||
|
||||
+5
-3
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <luma/detail/errors.hpp>
|
||||
|
||||
|
||||
struct luma::asset_cache::in_flight_load {
|
||||
std::condition_variable ready{};
|
||||
std::thread::id owner{};
|
||||
@@ -23,11 +24,12 @@ struct luma::asset_cache::in_flight_load {
|
||||
std::bool_t done{false};
|
||||
};
|
||||
|
||||
luma::asset_cache::asset_cache(luma::renderer_resources& resources) : resources_{&resources} {
|
||||
luma::asset_cache::asset_cache(luma::renderer_resources &resources)
|
||||
: resources_{&resources} {
|
||||
assert(resources_ != nullptr);
|
||||
}
|
||||
|
||||
auto luma::asset_cache::texture(std::string const& path) -> std::expected<std::shared_ptr<luma::texture>, modulus::error> {
|
||||
auto luma::asset_cache::texture(std::string const &path) -> std::expected<std::shared_ptr<luma::texture>, modulus::error> {
|
||||
assert(resources_ != nullptr);
|
||||
if (path.empty()) {
|
||||
return luma::detail::fail<std::shared_ptr<luma::texture>>(luma::detail::error_invalid_argument, "texture path must not be empty");
|
||||
@@ -68,7 +70,7 @@ auto luma::asset_cache::texture(std::string const& path) -> std::expected<std::s
|
||||
modulus::log_debug("luma texture loading: " + path);
|
||||
try {
|
||||
loaded = resources_->load_texture(path);
|
||||
} catch (std::exception const& error) {
|
||||
} catch (std::exception const &error) {
|
||||
loaded = luma::detail::fail<std::shared_ptr<luma::texture>>(luma::detail::error_internal, std::string{"texture load failed: "} + error.what());
|
||||
} catch (...) {
|
||||
loaded = luma::detail::fail<std::shared_ptr<luma::texture>>(luma::detail::error_internal, "texture load failed with an unknown exception");
|
||||
|
||||
+28
-26
@@ -15,13 +15,13 @@
|
||||
#include <luma/detail/errors.hpp>
|
||||
#include <luma/theme.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto json_options = modulus::json_config::options{
|
||||
.category = "luma",
|
||||
.invalid_argument_code = luma::detail::error_invalid_argument,
|
||||
.not_found_code = luma::detail::error_not_found
|
||||
};
|
||||
.not_found_code = luma::detail::error_not_found};
|
||||
|
||||
auto invalid_display(std::string message) -> std::expected<luma::display_config, modulus::error> {
|
||||
assert(!message.empty());
|
||||
@@ -40,7 +40,7 @@ auto invalid_application(std::string message) -> std::expected<luma::application
|
||||
|
||||
}
|
||||
|
||||
auto luma::validate(luma::display_config const& config) -> std::expected<void, modulus::error> {
|
||||
auto luma::validate(luma::display_config const &config) -> std::expected<void, modulus::error> {
|
||||
if (config.width <= 0) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, "display.width must be positive");
|
||||
}
|
||||
@@ -59,14 +59,14 @@ auto luma::validate(luma::display_config const& config) -> std::expected<void, m
|
||||
return {};
|
||||
}
|
||||
|
||||
auto luma::validate(luma::touch_config const& config) -> std::expected<void, modulus::error> {
|
||||
auto luma::validate(luma::touch_config const &config) -> std::expected<void, modulus::error> {
|
||||
if (config.max_points <= 0) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, "touch.max_points must be positive");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
auto luma::validate(luma::application_config const& config) -> std::expected<void, modulus::error> {
|
||||
auto luma::validate(luma::application_config const &config) -> std::expected<void, modulus::error> {
|
||||
if (config.title.empty()) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, "title must not be empty");
|
||||
}
|
||||
@@ -82,18 +82,19 @@ auto luma::validate(luma::application_config const& config) -> std::expected<voi
|
||||
return {};
|
||||
}
|
||||
|
||||
auto luma::parse_display_config(boost::json::value const& value) -> std::expected<luma::display_config, modulus::error> {
|
||||
auto luma::parse_display_config(boost::json::value const &value) -> std::expected<luma::display_config, modulus::error> {
|
||||
auto object = modulus::json_config::require_object(value, "display", json_options);
|
||||
if (!object) {
|
||||
return invalid_display(std::string{object.error().message()});
|
||||
}
|
||||
auto unknown = modulus::json_config::reject_unknown_fields(**object, {
|
||||
"width",
|
||||
"height",
|
||||
"target_fps",
|
||||
"idle_fps",
|
||||
"fullscreen",
|
||||
}, "display", json_options);
|
||||
"width",
|
||||
"height",
|
||||
"target_fps",
|
||||
"idle_fps",
|
||||
"fullscreen",
|
||||
},
|
||||
"display", json_options);
|
||||
if (!unknown) {
|
||||
return invalid_display(std::string{unknown.error().message()});
|
||||
}
|
||||
@@ -129,7 +130,7 @@ auto luma::parse_display_config(boost::json::value const& value) -> std::expecte
|
||||
return modulus::success(config);
|
||||
}
|
||||
|
||||
auto luma::parse_touch_config(boost::json::value const& value) -> std::expected<luma::touch_config, modulus::error> {
|
||||
auto luma::parse_touch_config(boost::json::value const &value) -> std::expected<luma::touch_config, modulus::error> {
|
||||
auto object = modulus::json_config::require_object(value, "touch", json_options);
|
||||
if (!object) {
|
||||
return invalid_touch(std::string{object.error().message()});
|
||||
@@ -150,17 +151,18 @@ auto luma::parse_touch_config(boost::json::value const& value) -> std::expected<
|
||||
return modulus::success(config);
|
||||
}
|
||||
|
||||
auto luma::parse_application_config(boost::json::value const& value) -> std::expected<luma::application_config, modulus::error> {
|
||||
auto luma::parse_application_config(boost::json::value const &value) -> std::expected<luma::application_config, modulus::error> {
|
||||
auto object = modulus::json_config::require_object(value, "application config", json_options);
|
||||
if (!object) {
|
||||
return invalid_application(std::string{object.error().message()});
|
||||
}
|
||||
auto unknown = modulus::json_config::reject_unknown_fields(**object, {
|
||||
"title",
|
||||
"display",
|
||||
"touch",
|
||||
"theme",
|
||||
}, "application config", json_options);
|
||||
"title",
|
||||
"display",
|
||||
"touch",
|
||||
"theme",
|
||||
},
|
||||
"application config", json_options);
|
||||
if (!unknown) {
|
||||
return invalid_application(std::string{unknown.error().message()});
|
||||
}
|
||||
@@ -170,21 +172,21 @@ auto luma::parse_application_config(boost::json::value const& value) -> std::exp
|
||||
return invalid_application(std::string{title.error().message()});
|
||||
}
|
||||
config.title = *title;
|
||||
if (auto const* display = (*object)->if_contains("display")) {
|
||||
if (auto const *display = (*object)->if_contains("display")) {
|
||||
auto parsed = luma::parse_display_config(*display);
|
||||
if (!parsed) {
|
||||
return invalid_application(std::string{parsed.error().message()});
|
||||
}
|
||||
config.display = *parsed;
|
||||
}
|
||||
if (auto const* touch = (*object)->if_contains("touch")) {
|
||||
if (auto const *touch = (*object)->if_contains("touch")) {
|
||||
auto parsed = luma::parse_touch_config(*touch);
|
||||
if (!parsed) {
|
||||
return invalid_application(std::string{parsed.error().message()});
|
||||
}
|
||||
config.touch = *parsed;
|
||||
}
|
||||
if (auto const* theme = (*object)->if_contains("theme")) {
|
||||
if (auto const *theme = (*object)->if_contains("theme")) {
|
||||
auto parsed = luma::parse_theme(*theme);
|
||||
if (!parsed) {
|
||||
return invalid_application(std::string{parsed.error().message()});
|
||||
@@ -197,7 +199,7 @@ auto luma::parse_application_config(boost::json::value const& value) -> std::exp
|
||||
return modulus::success(config);
|
||||
}
|
||||
|
||||
auto luma::load_application_config(std::string const& path) -> std::expected<luma::application_config, modulus::error> {
|
||||
auto luma::load_application_config(std::string const &path) -> std::expected<luma::application_config, modulus::error> {
|
||||
auto value = modulus::json_config::load_value(path, json_options);
|
||||
if (!value) {
|
||||
return modulus::fail<luma::application_config>(value.error());
|
||||
@@ -205,7 +207,7 @@ auto luma::load_application_config(std::string const& path) -> std::expected<lum
|
||||
return luma::parse_application_config(*value);
|
||||
}
|
||||
|
||||
auto luma::to_json(luma::display_config const& config) -> boost::json::value {
|
||||
auto luma::to_json(luma::display_config const &config) -> boost::json::value {
|
||||
return boost::json::object{
|
||||
{
|
||||
"width",
|
||||
@@ -230,7 +232,7 @@ auto luma::to_json(luma::display_config const& config) -> boost::json::value {
|
||||
};
|
||||
}
|
||||
|
||||
auto luma::to_json(luma::touch_config const& config) -> boost::json::value {
|
||||
auto luma::to_json(luma::touch_config const &config) -> boost::json::value {
|
||||
return boost::json::object{
|
||||
{
|
||||
"max_points",
|
||||
@@ -239,7 +241,7 @@ auto luma::to_json(luma::touch_config const& config) -> boost::json::value {
|
||||
};
|
||||
}
|
||||
|
||||
auto luma::to_json(luma::application_config const& config) -> boost::json::value {
|
||||
auto luma::to_json(luma::application_config const &config) -> boost::json::value {
|
||||
return boost::json::object{
|
||||
{
|
||||
"title",
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <modulus.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
auto valid_rect(luma::rect value) -> std::bool_t {
|
||||
@@ -22,7 +23,7 @@ auto luma::control::arrange(luma::rect bounds) -> void {
|
||||
bounds_ = bounds;
|
||||
}
|
||||
|
||||
auto luma::control::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto luma::control::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
static_cast<void>(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
+177
-161
@@ -27,21 +27,21 @@
|
||||
#include <luma/render.hpp>
|
||||
#include <luma/theme.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto panel_padding = luma::edge_insets{
|
||||
.left = 8.0F,
|
||||
.top = 8.0F,
|
||||
.right = 8.0F,
|
||||
.bottom = 8.0F
|
||||
};
|
||||
.bottom = 8.0F};
|
||||
constexpr auto scroll_drag_threshold = std::float32_t{6.0F};
|
||||
|
||||
class label_control final : public luma::control {
|
||||
public:
|
||||
explicit label_control(std::string text);
|
||||
auto measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context& context, luma::theme const& theme) const -> void override;
|
||||
auto measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context &context, luma::theme const &theme) const -> void override;
|
||||
private:
|
||||
std::string text_;
|
||||
};
|
||||
@@ -49,9 +49,9 @@ private:
|
||||
class button_control final : public luma::control {
|
||||
public:
|
||||
button_control(std::string text, std::function<void()> on_click);
|
||||
auto measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context& context, luma::theme const& theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const& event) -> std::bool_t override;
|
||||
auto measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context &context, luma::theme const &theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const &event) -> std::bool_t override;
|
||||
auto focusable() const -> std::bool_t override;
|
||||
private:
|
||||
std::string text_;
|
||||
@@ -62,9 +62,9 @@ private:
|
||||
class text_field_control final : public luma::control {
|
||||
public:
|
||||
explicit text_field_control(std::shared_ptr<luma::text_input_state> input);
|
||||
auto measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context& context, luma::theme const& theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const& event) -> std::bool_t override;
|
||||
auto measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context &context, luma::theme const &theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const &event) -> std::bool_t override;
|
||||
auto invalidate_layout() -> void override;
|
||||
auto bind_layout_invalidator(std::function<void()> invalidator) -> void override;
|
||||
auto focusable() const -> std::bool_t override;
|
||||
@@ -81,9 +81,9 @@ class soft_keyboard_control final : public luma::control {
|
||||
public:
|
||||
soft_keyboard_control(luma::keyboard_layout layout, std::function<void(std::string)> on_text, std::function<void()> on_enter);
|
||||
soft_keyboard_control(luma::keyboard_layout layout, std::shared_ptr<luma::text_input_state> input, std::function<void()> on_enter);
|
||||
auto measure(luma::draw_context&, luma::theme const&, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context& context, luma::theme const& theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const& event) -> std::bool_t override;
|
||||
auto measure(luma::draw_context &, luma::theme const &, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context &context, luma::theme const &theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const &event) -> std::bool_t override;
|
||||
private:
|
||||
luma::keyboard_layout layout_;
|
||||
std::function<void(std::string)> on_text_;
|
||||
@@ -94,10 +94,10 @@ private:
|
||||
class panel_control final : public luma::control {
|
||||
public:
|
||||
explicit panel_control(std::vector<std::unique_ptr<luma::control>> children);
|
||||
auto measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size override;
|
||||
auto measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size override;
|
||||
auto arrange(luma::rect bounds) -> void override;
|
||||
auto draw(luma::draw_context& context, luma::theme const& theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const& event) -> std::bool_t override;
|
||||
auto draw(luma::draw_context &context, luma::theme const &theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const &event) -> std::bool_t override;
|
||||
auto invalidate_layout() -> void override;
|
||||
auto bind_layout_invalidator(std::function<void()> invalidator) -> void override;
|
||||
private:
|
||||
@@ -107,8 +107,8 @@ private:
|
||||
class spacer_control final : public luma::control {
|
||||
public:
|
||||
explicit spacer_control(luma::size value);
|
||||
auto measure(luma::draw_context&, luma::theme const&, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context&, luma::theme const&) const -> void override;
|
||||
auto measure(luma::draw_context &, luma::theme const &, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context &, luma::theme const &) const -> void override;
|
||||
private:
|
||||
luma::size value_;
|
||||
};
|
||||
@@ -116,8 +116,8 @@ private:
|
||||
class progress_bar_control final : public luma::control {
|
||||
public:
|
||||
explicit progress_bar_control(std::float32_t value);
|
||||
auto measure(luma::draw_context&, luma::theme const&, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context& context, luma::theme const& theme) const -> void override;
|
||||
auto measure(luma::draw_context &, luma::theme const &, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context &context, luma::theme const &theme) const -> void override;
|
||||
private:
|
||||
std::float32_t value_;
|
||||
};
|
||||
@@ -125,8 +125,8 @@ private:
|
||||
class image_control final : public luma::control {
|
||||
public:
|
||||
image_control(std::shared_ptr<luma::texture> texture, luma::image_fit fit);
|
||||
auto measure(luma::draw_context&, luma::theme const&, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context& context, luma::theme const& theme) const -> void override;
|
||||
auto measure(luma::draw_context &, luma::theme const &, luma::constraints limits) -> luma::size override;
|
||||
auto draw(luma::draw_context &context, luma::theme const &theme) const -> void override;
|
||||
private:
|
||||
std::shared_ptr<luma::texture> texture_;
|
||||
luma::image_fit fit_;
|
||||
@@ -135,10 +135,10 @@ private:
|
||||
class linear_control final : public luma::control {
|
||||
public:
|
||||
linear_control(luma::direction direction, std::vector<std::unique_ptr<luma::control>> children, std::float32_t gap, luma::alignment align);
|
||||
auto measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size override;
|
||||
auto measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size override;
|
||||
auto arrange(luma::rect bounds) -> void override;
|
||||
auto draw(luma::draw_context& context, luma::theme const& theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const& event) -> std::bool_t override;
|
||||
auto draw(luma::draw_context &context, luma::theme const &theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const &event) -> std::bool_t override;
|
||||
auto invalidate_layout() -> void override;
|
||||
auto bind_layout_invalidator(std::function<void()> invalidator) -> void override;
|
||||
private:
|
||||
@@ -152,10 +152,10 @@ private:
|
||||
class stack_control final : public luma::control {
|
||||
public:
|
||||
explicit stack_control(std::vector<std::unique_ptr<luma::control>> children);
|
||||
auto measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size override;
|
||||
auto measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size override;
|
||||
auto arrange(luma::rect bounds) -> void override;
|
||||
auto draw(luma::draw_context& context, luma::theme const& theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const& event) -> std::bool_t override;
|
||||
auto draw(luma::draw_context &context, luma::theme const &theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const &event) -> std::bool_t override;
|
||||
auto invalidate_layout() -> void override;
|
||||
auto bind_layout_invalidator(std::function<void()> invalidator) -> void override;
|
||||
private:
|
||||
@@ -165,10 +165,10 @@ private:
|
||||
class scroll_view_control final : public luma::control {
|
||||
public:
|
||||
scroll_view_control(std::unique_ptr<luma::control> child, std::bool_t vertical, std::bool_t horizontal);
|
||||
auto measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size override;
|
||||
auto measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size override;
|
||||
auto arrange(luma::rect bounds) -> void override;
|
||||
auto draw(luma::draw_context& context, luma::theme const& theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const& event) -> std::bool_t override;
|
||||
auto draw(luma::draw_context &context, luma::theme const &theme) const -> void override;
|
||||
auto handle_touch(luma::touch_event const &event) -> std::bool_t override;
|
||||
auto invalidate_layout() -> void override;
|
||||
auto bind_layout_invalidator(std::function<void()> invalidator) -> void override;
|
||||
private:
|
||||
@@ -177,7 +177,7 @@ private:
|
||||
luma::point last{};
|
||||
std::bool_t child_active{false};
|
||||
};
|
||||
auto handle_point(luma::touch_event const& event, luma::touch_point const& point) -> std::bool_t;
|
||||
auto handle_point(luma::touch_event const &event, luma::touch_point const &point) -> std::bool_t;
|
||||
auto invalidate_child_measure() -> void;
|
||||
auto arrange_child() -> void;
|
||||
auto clamp_offset() -> void;
|
||||
@@ -202,9 +202,9 @@ auto finite_size(luma::size value) -> std::bool_t {
|
||||
|
||||
auto finite_rect(luma::rect value) -> std::bool_t {
|
||||
return std::isfinite(value.x) && std::isfinite(value.y) && finite_size({
|
||||
.width = value.width,
|
||||
.height = value.height,
|
||||
});
|
||||
.width = value.width,
|
||||
.height = value.height,
|
||||
});
|
||||
}
|
||||
|
||||
auto finite_constraints(luma::constraints value) -> std::bool_t {
|
||||
@@ -219,11 +219,11 @@ auto same_constraints(luma::constraints left, luma::constraints right) -> std::b
|
||||
return same_size(left.min, right.min) && same_size(left.max, right.max);
|
||||
}
|
||||
|
||||
auto same_text_style(luma::text_style const& left, luma::text_style const& right) -> std::bool_t {
|
||||
auto same_text_style(luma::text_style const &left, luma::text_style const &right) -> std::bool_t {
|
||||
return left.font == right.font && left.size == right.size && left.line_height == right.line_height;
|
||||
}
|
||||
|
||||
auto same_theme_metrics(luma::theme const& left, luma::theme const& right) -> std::bool_t {
|
||||
auto same_theme_metrics(luma::theme const &left, luma::theme const &right) -> std::bool_t {
|
||||
return same_text_style(left.body, right.body) && same_text_style(left.title, right.title) && left.radius == right.radius && left.spacing == right.spacing;
|
||||
}
|
||||
|
||||
@@ -232,26 +232,26 @@ auto invalid(std::string message) -> std::expected<VALUE, modulus::error> {
|
||||
return luma::detail::fail<VALUE>(luma::detail::error_invalid_argument, std::move(message));
|
||||
}
|
||||
|
||||
auto throw_error(modulus::error const& error) -> void {
|
||||
auto throw_error(modulus::error const &error) -> void {
|
||||
throw std::runtime_error(error.describe());
|
||||
}
|
||||
|
||||
auto throw_if_failed(std::expected<void, modulus::error> const& status) -> void {
|
||||
auto throw_if_failed(std::expected<void, modulus::error> const &status) -> void {
|
||||
if (!status) {
|
||||
throw_error(status.error());
|
||||
}
|
||||
}
|
||||
|
||||
auto make_event(luma::touch_event const& event, luma::touch_point point) -> luma::touch_event {
|
||||
auto make_event(luma::touch_event const &event, luma::touch_point point) -> luma::touch_event {
|
||||
return {
|
||||
.points = {point},
|
||||
.time = event.time,
|
||||
};
|
||||
}
|
||||
|
||||
auto dispatch_to_children(std::vector<std::unique_ptr<luma::control>>& children, luma::touch_event const& event) -> std::bool_t {
|
||||
auto dispatch_to_children(std::vector<std::unique_ptr<luma::control>> &children, luma::touch_event const &event) -> std::bool_t {
|
||||
auto handled = false;
|
||||
for (auto const& point : event.points) {
|
||||
for (auto const &point : event.points) {
|
||||
auto single = make_event(event, point);
|
||||
for (auto current = children.rbegin(); current != children.rend(); ++current) {
|
||||
assert(*current != nullptr);
|
||||
@@ -264,8 +264,8 @@ auto dispatch_to_children(std::vector<std::unique_ptr<luma::control>>& children,
|
||||
return handled;
|
||||
}
|
||||
|
||||
auto find_point(luma::touch_event const& event, std::int32_t id) -> luma::touch_point const* {
|
||||
for (auto const& point : event.points) {
|
||||
auto find_point(luma::touch_event const &event, std::int32_t id) -> luma::touch_point const * {
|
||||
for (auto const &point : event.points) {
|
||||
if (point.id == id) {
|
||||
return &point;
|
||||
}
|
||||
@@ -273,8 +273,8 @@ auto find_point(luma::touch_event const& event, std::int32_t id) -> luma::touch_
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto first_point_with_phase(luma::touch_event const& event, luma::touch_phase phase) -> luma::touch_point const* {
|
||||
for (auto const& point : event.points) {
|
||||
auto first_point_with_phase(luma::touch_event const &event, luma::touch_phase phase) -> luma::touch_point const * {
|
||||
for (auto const &point : event.points) {
|
||||
if (point.phase == phase) {
|
||||
return &point;
|
||||
}
|
||||
@@ -282,8 +282,8 @@ auto first_point_with_phase(luma::touch_event const& event, luma::touch_phase ph
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto first_point_inside(luma::touch_event const& event, luma::rect bounds, luma::touch_phase phase) -> luma::touch_point const* {
|
||||
for (auto const& point : event.points) {
|
||||
auto first_point_inside(luma::touch_event const &event, luma::rect bounds, luma::touch_phase phase) -> luma::touch_point const * {
|
||||
for (auto const &point : event.points) {
|
||||
if (point.phase == phase && luma::contains(bounds, point.position)) {
|
||||
return &point;
|
||||
}
|
||||
@@ -291,13 +291,13 @@ auto first_point_inside(luma::touch_event const& event, luma::rect bounds, luma:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto valid_keyboard_layout(luma::keyboard_layout const& layout) -> std::expected<void, modulus::error> {
|
||||
auto valid_keyboard_layout(luma::keyboard_layout const &layout) -> std::expected<void, modulus::error> {
|
||||
if (layout.rows.empty()) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, "keyboard layout must contain at least one row");
|
||||
}
|
||||
auto has_key = false;
|
||||
for (auto const& row : layout.rows) {
|
||||
for (auto const& key : row) {
|
||||
for (auto const &row : layout.rows) {
|
||||
for (auto const &key : row) {
|
||||
has_key = true;
|
||||
if (key.label.empty()) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, "keyboard key label must not be empty");
|
||||
@@ -310,8 +310,8 @@ auto valid_keyboard_layout(luma::keyboard_layout const& layout) -> std::expected
|
||||
return {};
|
||||
}
|
||||
|
||||
auto validate_children(std::vector<std::unique_ptr<luma::control>> const& children) -> std::expected<void, modulus::error> {
|
||||
for (auto const& child : children) {
|
||||
auto validate_children(std::vector<std::unique_ptr<luma::control>> const &children) -> std::expected<void, modulus::error> {
|
||||
for (auto const &child : children) {
|
||||
if (child == nullptr) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, "child control must not be null");
|
||||
}
|
||||
@@ -319,7 +319,7 @@ auto validate_children(std::vector<std::unique_ptr<luma::control>> const& childr
|
||||
return {};
|
||||
}
|
||||
|
||||
auto centered_text_origin(luma::draw_context& context, std::string const& text, luma::rect bounds, luma::text_style const& style) -> luma::point {
|
||||
auto centered_text_origin(luma::draw_context &context, std::string const &text, luma::rect bounds, luma::text_style const &style) -> luma::point {
|
||||
assert(finite_rect(bounds));
|
||||
auto measured = context.measure_text(text, style);
|
||||
assert(finite_size(measured));
|
||||
@@ -359,52 +359,56 @@ auto fit_rect(luma::rect bounds, luma::texture_info info, luma::image_fit fit) -
|
||||
};
|
||||
}
|
||||
|
||||
label_control::label_control(std::string text) : text_{std::move(text)} {
|
||||
label_control::label_control(std::string text)
|
||||
: text_{std::move(text)} {
|
||||
assert(!text_.empty());
|
||||
}
|
||||
|
||||
auto label_control::measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size {
|
||||
auto label_control::measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
auto measured = context.measure_text(text_, theme.body);
|
||||
assert(finite_size(measured));
|
||||
return luma::clamp(measured, limits);
|
||||
}
|
||||
|
||||
auto label_control::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
auto label_control::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
assert(!text_.empty());
|
||||
context.draw_text(text_, {
|
||||
.x = bounds().x,
|
||||
.y = bounds().y,
|
||||
}, theme.body, enabled() ? theme.text : theme.text_muted);
|
||||
.x = bounds().x,
|
||||
.y = bounds().y,
|
||||
},
|
||||
theme.body, enabled() ? theme.text : theme.text_muted);
|
||||
}
|
||||
|
||||
button_control::button_control(std::string text, std::function<void()> on_click) : text_{std::move(text)}, on_click_{std::move(on_click)} {
|
||||
button_control::button_control(std::string text, std::function<void()> on_click)
|
||||
: text_{std::move(text)}, on_click_{std::move(on_click)} {
|
||||
assert(!text_.empty());
|
||||
assert(on_click_);
|
||||
}
|
||||
|
||||
auto button_control::measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size {
|
||||
auto button_control::measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
auto text_size = context.measure_text(text_, theme.body);
|
||||
assert(finite_size(text_size));
|
||||
return luma::clamp({
|
||||
.width = text_size.width + 32.0F,
|
||||
.height = std::max(std::float32_t{48}, text_size.height + std::float32_t{16}),
|
||||
}, limits);
|
||||
.width = text_size.width + 32.0F,
|
||||
.height = std::max(std::float32_t{48}, text_size.height + std::float32_t{16}),
|
||||
},
|
||||
limits);
|
||||
}
|
||||
|
||||
auto button_control::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
auto button_control::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
auto fill = pressed_id_.has_value() ? theme.surface_active : theme.surface;
|
||||
context.fill_rect(bounds(), fill);
|
||||
context.stroke_rect(bounds(), 1.0F, focused() ? theme.accent : theme.border);
|
||||
context.draw_text(text_, centered_text_origin(context, text_, bounds(), theme.body), theme.body, enabled() ? theme.text : theme.text_muted);
|
||||
}
|
||||
|
||||
auto button_control::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto button_control::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
if (!enabled()) {
|
||||
return false;
|
||||
}
|
||||
auto const* point = pressed_id_ ? find_point(event, *pressed_id_) : first_point_inside(event, bounds(), luma::touch_phase::DOWN);
|
||||
auto const *point = pressed_id_ ? find_point(event, *pressed_id_) : first_point_inside(event, bounds(), luma::touch_phase::DOWN);
|
||||
if (point == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -434,11 +438,12 @@ auto button_control::focusable() const -> std::bool_t {
|
||||
return true;
|
||||
}
|
||||
|
||||
text_field_control::text_field_control(std::shared_ptr<luma::text_input_state> input) : input_{std::move(input)} {
|
||||
text_field_control::text_field_control(std::shared_ptr<luma::text_input_state> input)
|
||||
: input_{std::move(input)} {
|
||||
assert(input_ != nullptr);
|
||||
}
|
||||
|
||||
auto text_field_control::measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size {
|
||||
auto text_field_control::measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
auto version = input_->version();
|
||||
if (cached_limits_ && cached_style_ && same_constraints(*cached_limits_, limits) && same_text_style(*cached_style_, theme.body) && cached_version_ == version) {
|
||||
@@ -451,13 +456,14 @@ auto text_field_control::measure(luma::draw_context& context, luma::theme const&
|
||||
cached_style_ = theme.body;
|
||||
cached_version_ = version;
|
||||
cached_size_ = luma::clamp({
|
||||
.width = std::max(std::float32_t{240}, measured.width + std::float32_t{24}),
|
||||
.height = std::max(std::float32_t{48}, measured.height + std::float32_t{16}),
|
||||
}, limits);
|
||||
.width = std::max(std::float32_t{240}, measured.width + std::float32_t{24}),
|
||||
.height = std::max(std::float32_t{48}, measured.height + std::float32_t{16}),
|
||||
},
|
||||
limits);
|
||||
return cached_size_;
|
||||
}
|
||||
|
||||
auto text_field_control::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
auto text_field_control::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
context.fill_rect(bounds(), theme.surface);
|
||||
context.stroke_rect(bounds(), 1.0F, input_->focused() ? theme.accent : theme.border);
|
||||
auto options = input_->snapshot();
|
||||
@@ -468,16 +474,17 @@ auto text_field_control::draw(luma::draw_context& context, luma::theme const& th
|
||||
text = std::string(options.text.size(), '*');
|
||||
}
|
||||
context.draw_text(text, {
|
||||
.x = bounds().x + 12.0F,
|
||||
.y = bounds().y + 8.0F,
|
||||
}, theme.body, options.text.empty() ? theme.text_muted : theme.text);
|
||||
.x = bounds().x + 12.0F,
|
||||
.y = bounds().y + 8.0F,
|
||||
},
|
||||
theme.body, options.text.empty() ? theme.text_muted : theme.text);
|
||||
}
|
||||
|
||||
auto text_field_control::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto text_field_control::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
if (!enabled()) {
|
||||
return false;
|
||||
}
|
||||
auto const* point = first_point_inside(event, bounds(), luma::touch_phase::DOWN);
|
||||
auto const *point = first_point_inside(event, bounds(), luma::touch_phase::DOWN);
|
||||
if (point == nullptr) {
|
||||
point = first_point_with_phase(event, luma::touch_phase::DOWN);
|
||||
}
|
||||
@@ -513,28 +520,31 @@ auto text_field_control::focusable() const -> std::bool_t {
|
||||
return true;
|
||||
}
|
||||
|
||||
soft_keyboard_control::soft_keyboard_control(luma::keyboard_layout layout, std::function<void(std::string)> on_text, std::function<void()> on_enter) : layout_{std::move(layout)}, on_text_{std::move(on_text)}, on_enter_{std::move(on_enter)} {
|
||||
soft_keyboard_control::soft_keyboard_control(luma::keyboard_layout layout, std::function<void(std::string)> on_text, std::function<void()> on_enter)
|
||||
: layout_{std::move(layout)}, on_text_{std::move(on_text)}, on_enter_{std::move(on_enter)} {
|
||||
assert(!layout_.rows.empty());
|
||||
assert(on_text_);
|
||||
}
|
||||
|
||||
soft_keyboard_control::soft_keyboard_control(luma::keyboard_layout layout, std::shared_ptr<luma::text_input_state> input, std::function<void()> on_enter) : layout_{std::move(layout)}, on_enter_{std::move(on_enter)}, input_{std::move(input)} {
|
||||
soft_keyboard_control::soft_keyboard_control(luma::keyboard_layout layout, std::shared_ptr<luma::text_input_state> input, std::function<void()> on_enter)
|
||||
: layout_{std::move(layout)}, on_enter_{std::move(on_enter)}, input_{std::move(input)} {
|
||||
assert(!layout_.rows.empty());
|
||||
assert(input_ != nullptr);
|
||||
}
|
||||
|
||||
auto soft_keyboard_control::measure(luma::draw_context&, luma::theme const&, luma::constraints limits) -> luma::size {
|
||||
auto soft_keyboard_control::measure(luma::draw_context &, luma::theme const &, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
return luma::clamp({
|
||||
.width = 640.0F,
|
||||
.height = static_cast<std::float32_t>(layout_.rows.size()) * 56.0F,
|
||||
}, limits);
|
||||
.width = 640.0F,
|
||||
.height = static_cast<std::float32_t>(layout_.rows.size()) * 56.0F,
|
||||
},
|
||||
limits);
|
||||
}
|
||||
|
||||
auto soft_keyboard_control::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
auto soft_keyboard_control::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
auto key_height = bounds().height / static_cast<std::float32_t>(layout_.rows.size());
|
||||
for (auto row_index = std::size_t{}; row_index < layout_.rows.size(); ++row_index) {
|
||||
auto const& row = layout_.rows[row_index];
|
||||
auto const &row = layout_.rows[row_index];
|
||||
if (row.empty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -549,27 +559,28 @@ auto soft_keyboard_control::draw(luma::draw_context& context, luma::theme const&
|
||||
context.fill_rect(rect, theme.surface);
|
||||
context.stroke_rect(rect, 1.0F, theme.border);
|
||||
context.draw_text(row[column_index].label, {
|
||||
.x = rect.x + 12.0F,
|
||||
.y = rect.y + 10.0F,
|
||||
}, theme.body, theme.text);
|
||||
.x = rect.x + 12.0F,
|
||||
.y = rect.y + 10.0F,
|
||||
},
|
||||
theme.body, theme.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto soft_keyboard_control::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto const* point = first_point_inside(event, bounds(), luma::touch_phase::UP);
|
||||
auto soft_keyboard_control::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
auto const *point = first_point_inside(event, bounds(), luma::touch_phase::UP);
|
||||
if (point == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto row_count = layout_.rows.size();
|
||||
assert(row_count > 0);
|
||||
auto row_index = std::min(row_count - 1, static_cast<std::size_t>((point->position.y - bounds().y) / (bounds().height / static_cast<std::float32_t>(row_count))));
|
||||
auto const& row = layout_.rows[row_index];
|
||||
auto const &row = layout_.rows[row_index];
|
||||
if (row.empty()) {
|
||||
return true;
|
||||
}
|
||||
auto column_index = std::min(row.size() - 1, static_cast<std::size_t>((point->position.x - bounds().x) / (bounds().width / static_cast<std::float32_t>(row.size()))));
|
||||
auto const& key = row[column_index];
|
||||
auto const &key = row[column_index];
|
||||
switch (key.kind) {
|
||||
case luma::keyboard_key_kind::CHARACTER:
|
||||
if (input_) {
|
||||
@@ -604,13 +615,14 @@ auto soft_keyboard_control::handle_touch(luma::touch_event const& event) -> std:
|
||||
return true;
|
||||
}
|
||||
|
||||
panel_control::panel_control(std::vector<std::unique_ptr<luma::control>> children) : children_{std::move(children)} {}
|
||||
panel_control::panel_control(std::vector<std::unique_ptr<luma::control>> children)
|
||||
: children_{std::move(children)} {}
|
||||
|
||||
auto panel_control::measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size {
|
||||
auto panel_control::measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
auto child_limits = luma::deflate(limits, panel_padding);
|
||||
auto result = child_limits.min;
|
||||
for (auto& child : children_) {
|
||||
for (auto &child : children_) {
|
||||
assert(child != nullptr);
|
||||
auto measured = child->measure(context, theme, child_limits);
|
||||
result.width = std::max(result.width, measured.width);
|
||||
@@ -625,26 +637,26 @@ auto panel_control::arrange(luma::rect bounds) -> void {
|
||||
assert(finite_rect(bounds));
|
||||
luma::control::arrange(bounds);
|
||||
auto content = luma::inset(bounds, panel_padding);
|
||||
for (auto& child : children_) {
|
||||
for (auto &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->arrange(content);
|
||||
}
|
||||
}
|
||||
|
||||
auto panel_control::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
auto panel_control::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
context.fill_rect(bounds(), theme.surface);
|
||||
for (auto const& child : children_) {
|
||||
for (auto const &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->draw(context, theme);
|
||||
}
|
||||
}
|
||||
|
||||
auto panel_control::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto panel_control::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
return dispatch_to_children(children_, event);
|
||||
}
|
||||
|
||||
auto panel_control::invalidate_layout() -> void {
|
||||
for (auto& child : children_) {
|
||||
for (auto &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->invalidate_layout();
|
||||
}
|
||||
@@ -652,62 +664,68 @@ auto panel_control::invalidate_layout() -> void {
|
||||
|
||||
auto panel_control::bind_layout_invalidator(std::function<void()> invalidator) -> void {
|
||||
luma::control::bind_layout_invalidator(invalidator);
|
||||
for (auto& child : children_) {
|
||||
for (auto &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->bind_layout_invalidator(invalidator);
|
||||
}
|
||||
}
|
||||
|
||||
spacer_control::spacer_control(luma::size value) : value_{value} {
|
||||
spacer_control::spacer_control(luma::size value)
|
||||
: value_{value} {
|
||||
assert(finite_size(value_));
|
||||
}
|
||||
|
||||
auto spacer_control::measure(luma::draw_context&, luma::theme const&, luma::constraints limits) -> luma::size {
|
||||
auto spacer_control::measure(luma::draw_context &, luma::theme const &, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
return luma::clamp(value_, limits);
|
||||
}
|
||||
|
||||
auto spacer_control::draw(luma::draw_context&, luma::theme const&) const -> void {}
|
||||
auto spacer_control::draw(luma::draw_context &, luma::theme const &) const -> void {}
|
||||
|
||||
progress_bar_control::progress_bar_control(std::float32_t value) : value_{std::clamp(value, std::float32_t{}, std::float32_t{1})} {
|
||||
progress_bar_control::progress_bar_control(std::float32_t value)
|
||||
: value_{std::clamp(value, std::float32_t{}, std::float32_t{1})} {
|
||||
assert(std::isfinite(value));
|
||||
}
|
||||
|
||||
auto progress_bar_control::measure(luma::draw_context&, luma::theme const&, luma::constraints limits) -> luma::size {
|
||||
auto progress_bar_control::measure(luma::draw_context &, luma::theme const &, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
return luma::clamp({
|
||||
.width = 160.0F,
|
||||
.height = 16.0F,
|
||||
}, limits);
|
||||
.width = 160.0F,
|
||||
.height = 16.0F,
|
||||
},
|
||||
limits);
|
||||
}
|
||||
|
||||
auto progress_bar_control::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
auto progress_bar_control::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
context.fill_rect(bounds(), theme.surface);
|
||||
context.fill_rect({
|
||||
.x = bounds().x,
|
||||
.y = bounds().y,
|
||||
.width = bounds().width * value_,
|
||||
.height = bounds().height,
|
||||
}, theme.accent);
|
||||
.x = bounds().x,
|
||||
.y = bounds().y,
|
||||
.width = bounds().width * value_,
|
||||
.height = bounds().height,
|
||||
},
|
||||
theme.accent);
|
||||
}
|
||||
|
||||
image_control::image_control(std::shared_ptr<luma::texture> texture, luma::image_fit fit) : texture_{std::move(texture)}, fit_{fit} {
|
||||
image_control::image_control(std::shared_ptr<luma::texture> texture, luma::image_fit fit)
|
||||
: texture_{std::move(texture)}, fit_{fit} {
|
||||
assert(texture_ != nullptr);
|
||||
}
|
||||
|
||||
auto image_control::measure(luma::draw_context&, luma::theme const&, luma::constraints limits) -> luma::size {
|
||||
auto image_control::measure(luma::draw_context &, luma::theme const &, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
auto info = texture_->info();
|
||||
if (info.width <= 0 || info.height <= 0) {
|
||||
return luma::clamp({}, limits);
|
||||
}
|
||||
return luma::clamp({
|
||||
.width = static_cast<std::float32_t>(info.width),
|
||||
.height = static_cast<std::float32_t>(info.height),
|
||||
}, limits);
|
||||
.width = static_cast<std::float32_t>(info.width),
|
||||
.height = static_cast<std::float32_t>(info.height),
|
||||
},
|
||||
limits);
|
||||
}
|
||||
|
||||
auto image_control::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
auto image_control::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
auto info = texture_->info();
|
||||
if (info.width <= 0 || info.height <= 0) {
|
||||
return;
|
||||
@@ -721,18 +739,19 @@ auto image_control::draw(luma::draw_context& context, luma::theme const& theme)
|
||||
context.draw_texture(*texture_, source, fit_rect(bounds(), info, fit_), theme.text);
|
||||
}
|
||||
|
||||
linear_control::linear_control(luma::direction direction, std::vector<std::unique_ptr<luma::control>> children, std::float32_t gap, luma::alignment align) : direction_{direction}, children_{std::move(children)}, gap_{gap}, align_{align} {
|
||||
linear_control::linear_control(luma::direction direction, std::vector<std::unique_ptr<luma::control>> children, std::float32_t gap, luma::alignment align)
|
||||
: direction_{direction}, children_{std::move(children)}, gap_{gap}, align_{align} {
|
||||
assert(std::isfinite(gap_));
|
||||
assert(gap_ >= 0.0F);
|
||||
}
|
||||
|
||||
auto linear_control::measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size {
|
||||
auto linear_control::measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
measured_.clear();
|
||||
measured_.reserve(children_.size());
|
||||
auto result = luma::size{};
|
||||
for (auto index = std::size_t{}; index < children_.size(); ++index) {
|
||||
auto& child = children_[index];
|
||||
auto &child = children_[index];
|
||||
assert(child != nullptr);
|
||||
auto child_limits = limits;
|
||||
if (direction_ == luma::direction::ROW) {
|
||||
@@ -762,7 +781,7 @@ auto linear_control::arrange(luma::rect bounds) -> void {
|
||||
luma::control::arrange(bounds);
|
||||
auto cursor = direction_ == luma::direction::ROW ? bounds.x : bounds.y;
|
||||
for (auto index = std::size_t{}; index < children_.size(); ++index) {
|
||||
auto& child = children_[index];
|
||||
auto &child = children_[index];
|
||||
assert(child != nullptr);
|
||||
auto measured = index < measured_.size() ? measured_[index] : luma::size{};
|
||||
auto child_bounds = bounds;
|
||||
@@ -791,20 +810,20 @@ auto linear_control::arrange(luma::rect bounds) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto linear_control::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
for (auto const& child : children_) {
|
||||
auto linear_control::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
for (auto const &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->draw(context, theme);
|
||||
}
|
||||
}
|
||||
|
||||
auto linear_control::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto linear_control::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
return dispatch_to_children(children_, event);
|
||||
}
|
||||
|
||||
auto linear_control::invalidate_layout() -> void {
|
||||
measured_.clear();
|
||||
for (auto& child : children_) {
|
||||
for (auto &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->invalidate_layout();
|
||||
}
|
||||
@@ -812,19 +831,19 @@ auto linear_control::invalidate_layout() -> void {
|
||||
|
||||
auto linear_control::bind_layout_invalidator(std::function<void()> invalidator) -> void {
|
||||
luma::control::bind_layout_invalidator(invalidator);
|
||||
for (auto& child : children_) {
|
||||
for (auto &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->bind_layout_invalidator(invalidator);
|
||||
}
|
||||
}
|
||||
|
||||
stack_control::stack_control(std::vector<std::unique_ptr<luma::control>> children)
|
||||
: children_{std::move(children)} {}
|
||||
|
||||
stack_control::stack_control(std::vector<std::unique_ptr<luma::control>> children) : children_{std::move(children)} {}
|
||||
|
||||
auto stack_control::measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size {
|
||||
auto stack_control::measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
auto result = limits.min;
|
||||
for (auto& child : children_) {
|
||||
for (auto &child : children_) {
|
||||
assert(child != nullptr);
|
||||
auto measured = child->measure(context, theme, limits);
|
||||
result.width = std::max(result.width, measured.width);
|
||||
@@ -836,25 +855,25 @@ auto stack_control::measure(luma::draw_context& context, luma::theme const& them
|
||||
auto stack_control::arrange(luma::rect bounds) -> void {
|
||||
assert(finite_rect(bounds));
|
||||
luma::control::arrange(bounds);
|
||||
for (auto& child : children_) {
|
||||
for (auto &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->arrange(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
auto stack_control::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
for (auto const& child : children_) {
|
||||
auto stack_control::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
for (auto const &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->draw(context, theme);
|
||||
}
|
||||
}
|
||||
|
||||
auto stack_control::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto stack_control::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
return dispatch_to_children(children_, event);
|
||||
}
|
||||
|
||||
auto stack_control::invalidate_layout() -> void {
|
||||
for (auto& child : children_) {
|
||||
for (auto &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->invalidate_layout();
|
||||
}
|
||||
@@ -862,19 +881,19 @@ auto stack_control::invalidate_layout() -> void {
|
||||
|
||||
auto stack_control::bind_layout_invalidator(std::function<void()> invalidator) -> void {
|
||||
luma::control::bind_layout_invalidator(invalidator);
|
||||
for (auto& child : children_) {
|
||||
for (auto &child : children_) {
|
||||
assert(child != nullptr);
|
||||
child->bind_layout_invalidator(invalidator);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
scroll_view_control::scroll_view_control(std::unique_ptr<luma::control> child, std::bool_t vertical, std::bool_t horizontal) : child_{std::move(child)}, vertical_{vertical}, horizontal_{horizontal} {
|
||||
scroll_view_control::scroll_view_control(std::unique_ptr<luma::control> child, std::bool_t vertical, std::bool_t horizontal)
|
||||
: child_{std::move(child)}, vertical_{vertical}, horizontal_{horizontal} {
|
||||
assert(child_ != nullptr);
|
||||
assert(vertical_ || horizontal_);
|
||||
}
|
||||
|
||||
auto scroll_view_control::measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size {
|
||||
auto scroll_view_control::measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size {
|
||||
assert(finite_constraints(limits));
|
||||
auto max = std::numeric_limits<std::float32_t>::max();
|
||||
auto child_limits = luma::constraints{
|
||||
@@ -903,21 +922,21 @@ auto scroll_view_control::arrange(luma::rect bounds) -> void {
|
||||
arrange_child();
|
||||
}
|
||||
|
||||
auto scroll_view_control::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
auto scroll_view_control::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
context.push_clip(bounds());
|
||||
child_->draw(context, theme);
|
||||
context.pop_clip();
|
||||
}
|
||||
|
||||
auto scroll_view_control::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto scroll_view_control::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
auto handled = false;
|
||||
for (auto const& point : event.points) {
|
||||
for (auto const &point : event.points) {
|
||||
handled = handle_point(event, point) || handled;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
auto scroll_view_control::handle_point(luma::touch_event const& event, luma::touch_point const& point) -> std::bool_t {
|
||||
auto scroll_view_control::handle_point(luma::touch_event const &event, luma::touch_point const &point) -> std::bool_t {
|
||||
if (point.phase == luma::touch_phase::DOWN) {
|
||||
if (!luma::contains(bounds(), point.position)) {
|
||||
return false;
|
||||
@@ -938,13 +957,11 @@ auto scroll_view_control::handle_point(luma::touch_event const& event, luma::tou
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto found = touches_.find(point.id);
|
||||
if (found == touches_.end()) {
|
||||
return false;
|
||||
}
|
||||
auto& state = found->second;
|
||||
|
||||
auto &state = found->second;
|
||||
if (point.phase == luma::touch_phase::MOVE) {
|
||||
auto delta = luma::point{
|
||||
.x = point.position.x - state.last.x,
|
||||
@@ -994,7 +1011,6 @@ auto scroll_view_control::handle_point(luma::touch_event const& event, luma::tou
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (point.phase == luma::touch_phase::UP || point.phase == luma::touch_phase::CANCEL) {
|
||||
auto const scrolling = scroll_id_ == point.id && dragging_;
|
||||
if (state.child_active) {
|
||||
@@ -1015,7 +1031,6 @@ auto scroll_view_control::handle_point(luma::touch_event const& event, luma::tou
|
||||
touches_.erase(found);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1073,7 +1088,7 @@ auto accepts_text(luma::text_input_mode mode, std::string_view current, std::str
|
||||
return true;
|
||||
}
|
||||
|
||||
auto validate_text_options(luma::text_field_options const& options) -> std::expected<void, modulus::error> {
|
||||
auto validate_text_options(luma::text_field_options const &options) -> std::expected<void, modulus::error> {
|
||||
if (options.max_length == 0) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, "text field max length must be positive");
|
||||
}
|
||||
@@ -1090,13 +1105,13 @@ auto callback_failure(std::string message) -> std::expected<void, modulus::error
|
||||
return luma::detail::fail<void>(luma::detail::error_internal, std::move(message));
|
||||
}
|
||||
|
||||
auto notify_text_changed(std::function<void(std::string)> const& callback, std::string value) -> std::expected<void, modulus::error> {
|
||||
auto notify_text_changed(std::function<void(std::string)> const &callback, std::string value) -> std::expected<void, modulus::error> {
|
||||
if (!callback) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
callback(std::move(value));
|
||||
} catch (std::exception const& error) {
|
||||
} catch (std::exception const &error) {
|
||||
return callback_failure(std::string{"text change callback failed: "} + error.what());
|
||||
} catch (...) {
|
||||
return callback_failure("text change callback failed with an unknown exception");
|
||||
@@ -1104,8 +1119,8 @@ auto notify_text_changed(std::function<void(std::string)> const& callback, std::
|
||||
return {};
|
||||
}
|
||||
|
||||
auto notify_layout_listeners(std::vector<std::function<void()>> const& listeners) -> void {
|
||||
for (auto const& listener : listeners) {
|
||||
auto notify_layout_listeners(std::vector<std::function<void()>> const &listeners) -> void {
|
||||
for (auto const &listener : listeners) {
|
||||
if (!listener) {
|
||||
continue;
|
||||
}
|
||||
@@ -1116,7 +1131,7 @@ auto notify_layout_listeners(std::vector<std::function<void()>> const& listeners
|
||||
}
|
||||
}
|
||||
|
||||
auto collect_layout_listeners(std::vector<luma::text_input_state::layout_listener>& listeners) -> std::vector<std::function<void()>> {
|
||||
auto collect_layout_listeners(std::vector<luma::text_input_state::layout_listener> &listeners) -> std::vector<std::function<void()>> {
|
||||
auto callbacks = std::vector<std::function<void()>>{};
|
||||
auto write = listeners.begin();
|
||||
for (auto read = listeners.begin(); read != listeners.end(); ++read) {
|
||||
@@ -1135,7 +1150,8 @@ auto collect_layout_listeners(std::vector<luma::text_input_state::layout_listene
|
||||
|
||||
}
|
||||
|
||||
luma::text_input_state::text_input_state(luma::text_field_options options, std::function<void(std::string)> on_change) : options_{std::move(options)}, on_change_{std::move(on_change)} {
|
||||
luma::text_input_state::text_input_state(luma::text_field_options options, std::function<void(std::string)> on_change)
|
||||
: options_{std::move(options)}, on_change_{std::move(on_change)} {
|
||||
assert(options_.max_length > 0);
|
||||
assert(on_change_);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <modulus.hpp>
|
||||
|
||||
|
||||
auto luma::detail::make_error(std::int32_t code, std::string message, modulus::source_location location) -> modulus::error {
|
||||
return modulus::make_error("luma", code, std::move(message), location);
|
||||
}
|
||||
|
||||
+43
-37
@@ -17,13 +17,14 @@
|
||||
#include <luma/render.hpp>
|
||||
#include <luma/theme.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class scope_exit {
|
||||
public:
|
||||
explicit scope_exit(std::function<void()> action);
|
||||
scope_exit(scope_exit const&) = delete;
|
||||
auto operator=(scope_exit const&) -> scope_exit& = delete;
|
||||
scope_exit(scope_exit const &) = delete;
|
||||
auto operator=(scope_exit const &) -> scope_exit & = delete;
|
||||
~scope_exit();
|
||||
private:
|
||||
std::function<void()> action_{};
|
||||
@@ -53,11 +54,11 @@ auto dialog_bounds(luma::rect bounds) -> luma::rect {
|
||||
|
||||
auto dialog_body_bounds(luma::rect bounds) -> luma::rect {
|
||||
return luma::inset(dialog_bounds(bounds), {
|
||||
16.0F,
|
||||
56.0F,
|
||||
16.0F,
|
||||
72.0F,
|
||||
});
|
||||
16.0F,
|
||||
56.0F,
|
||||
16.0F,
|
||||
72.0F,
|
||||
});
|
||||
}
|
||||
|
||||
auto accept_bounds(luma::rect bounds, std::bool_t has_cancel) -> luma::rect {
|
||||
@@ -82,18 +83,19 @@ auto cancel_bounds(luma::rect bounds) -> luma::rect {
|
||||
};
|
||||
}
|
||||
|
||||
auto draw_button(luma::draw_context& context, luma::theme const& theme, luma::rect bounds, std::string const& text, luma::color fill) -> void {
|
||||
auto draw_button(luma::draw_context &context, luma::theme const &theme, luma::rect bounds, std::string const &text, luma::color fill) -> void {
|
||||
context.fill_rect(bounds, fill);
|
||||
context.stroke_rect(bounds, 1.0F, theme.border);
|
||||
auto measured = context.measure_text(text, theme.body);
|
||||
context.draw_text(text, {
|
||||
.x = bounds.x + (bounds.width - measured.width) * 0.5F,
|
||||
.y = bounds.y + (bounds.height - measured.height) * 0.5F,
|
||||
}, theme.body, theme.text);
|
||||
.x = bounds.x + (bounds.width - measured.width) * 0.5F,
|
||||
.y = bounds.y + (bounds.height - measured.height) * 0.5F,
|
||||
},
|
||||
theme.body, theme.text);
|
||||
}
|
||||
|
||||
auto first_point_inside(luma::touch_event const& event, luma::rect bounds, luma::touch_phase phase) -> luma::touch_point const* {
|
||||
for (auto const& point : event.points) {
|
||||
auto first_point_inside(luma::touch_event const &event, luma::rect bounds, luma::touch_phase phase) -> luma::touch_point const * {
|
||||
for (auto const &point : event.points) {
|
||||
if (point.phase == phase && luma::contains(bounds, point.position)) {
|
||||
return &point;
|
||||
}
|
||||
@@ -101,8 +103,8 @@ auto first_point_inside(luma::touch_event const& event, luma::rect bounds, luma:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto any_point_inside(luma::touch_event const& event, luma::rect bounds) -> std::bool_t {
|
||||
for (auto const& point : event.points) {
|
||||
auto any_point_inside(luma::touch_event const &event, luma::rect bounds) -> std::bool_t {
|
||||
for (auto const &point : event.points) {
|
||||
if (luma::contains(bounds, point.position)) {
|
||||
return true;
|
||||
}
|
||||
@@ -110,13 +112,14 @@ auto any_point_inside(luma::touch_event const& event, luma::rect bounds) -> std:
|
||||
return false;
|
||||
}
|
||||
|
||||
auto throw_if_failed(std::expected<void, modulus::error> const& status) -> void {
|
||||
auto throw_if_failed(std::expected<void, modulus::error> const &status) -> void {
|
||||
if (!status) {
|
||||
throw std::runtime_error(status.error().describe());
|
||||
}
|
||||
}
|
||||
|
||||
scope_exit::scope_exit(std::function<void()> action) : action_{std::move(action)} {}
|
||||
scope_exit::scope_exit(std::function<void()> action)
|
||||
: action_{std::move(action)} {}
|
||||
|
||||
scope_exit::~scope_exit() {
|
||||
if (action_) {
|
||||
@@ -126,7 +129,8 @@ scope_exit::~scope_exit() {
|
||||
|
||||
}
|
||||
|
||||
luma::overlay_host::overlay_host(std::unique_ptr<luma::control> content) : content_{std::move(content)} {
|
||||
luma::overlay_host::overlay_host(std::unique_ptr<luma::control> content)
|
||||
: content_{std::move(content)} {
|
||||
assert(content_ != nullptr);
|
||||
}
|
||||
|
||||
@@ -163,7 +167,7 @@ auto luma::overlay_host::close_dialog(luma::dialog_result result) -> std::expect
|
||||
if (callback) {
|
||||
try {
|
||||
callback(result);
|
||||
} catch (std::exception const& error) {
|
||||
} catch (std::exception const &error) {
|
||||
return luma::detail::fail<void>(luma::detail::error_internal, std::string{"dialog close callback failed: "} + error.what());
|
||||
} catch (...) {
|
||||
return luma::detail::fail<void>(luma::detail::error_internal, "dialog close callback failed with an unknown exception");
|
||||
@@ -226,7 +230,7 @@ auto luma::overlay_host::tick(std::chrono::steady_clock::duration delta) -> std:
|
||||
return false;
|
||||
}
|
||||
|
||||
auto luma::overlay_host::measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size {
|
||||
auto luma::overlay_host::measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size {
|
||||
assert(content_ != nullptr);
|
||||
begin_traversal();
|
||||
auto guard = scope_exit{[this] {
|
||||
@@ -234,14 +238,14 @@ auto luma::overlay_host::measure(luma::draw_context& context, luma::theme const&
|
||||
}};
|
||||
auto measured = content_->measure(context, theme, limits);
|
||||
if (dialog_ && dialog_->body) {
|
||||
auto* body = dialog_->body.get();
|
||||
auto *body = dialog_->body.get();
|
||||
body->measure(context, theme, {
|
||||
.min = {},
|
||||
.max = {
|
||||
.width = limits.max.width * 0.8F,
|
||||
.height = limits.max.height * 0.8F,
|
||||
},
|
||||
});
|
||||
.min = {},
|
||||
.max = {
|
||||
.width = limits.max.width * 0.8F,
|
||||
.height = limits.max.height * 0.8F,
|
||||
},
|
||||
});
|
||||
}
|
||||
return measured;
|
||||
}
|
||||
@@ -255,12 +259,12 @@ auto luma::overlay_host::arrange(luma::rect bounds) -> void {
|
||||
luma::control::arrange(bounds);
|
||||
content_->arrange(bounds);
|
||||
if (dialog_ && dialog_->body) {
|
||||
auto* body = dialog_->body.get();
|
||||
auto *body = dialog_->body.get();
|
||||
body->arrange(dialog_body_bounds(bounds));
|
||||
}
|
||||
}
|
||||
|
||||
auto luma::overlay_host::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
auto luma::overlay_host::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
assert(content_ != nullptr);
|
||||
begin_traversal();
|
||||
auto guard = scope_exit{[this] {
|
||||
@@ -272,13 +276,14 @@ auto luma::overlay_host::draw(luma::draw_context& context, luma::theme const& th
|
||||
auto title = dialog_->title;
|
||||
auto accept_text = dialog_->accept_text;
|
||||
auto cancel_text = dialog_->cancel_text;
|
||||
auto* body = dialog_->body.get();
|
||||
auto *body = dialog_->body.get();
|
||||
context.fill_rect(dialog, theme.surface);
|
||||
context.stroke_rect(dialog, 2.0F, theme.accent);
|
||||
context.draw_text(title, {
|
||||
.x = dialog.x + 16.0F,
|
||||
.y = dialog.y + 16.0F,
|
||||
}, theme.title, theme.text);
|
||||
.x = dialog.x + 16.0F,
|
||||
.y = dialog.y + 16.0F,
|
||||
},
|
||||
theme.title, theme.text);
|
||||
body->draw(context, theme);
|
||||
auto has_cancel = !cancel_text.empty();
|
||||
if (has_cancel) {
|
||||
@@ -295,13 +300,14 @@ auto luma::overlay_host::draw(luma::draw_context& context, luma::theme const& th
|
||||
};
|
||||
context.fill_rect(toast_bounds, theme.surface_active);
|
||||
context.draw_text(toasts_.front().message, {
|
||||
.x = toast_bounds.x + 16.0F,
|
||||
.y = toast_bounds.y + 10.0F,
|
||||
}, theme.body, theme.text);
|
||||
.x = toast_bounds.x + 16.0F,
|
||||
.y = toast_bounds.y + 10.0F,
|
||||
},
|
||||
theme.body, theme.text);
|
||||
}
|
||||
}
|
||||
|
||||
auto luma::overlay_host::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto luma::overlay_host::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
assert(content_ != nullptr);
|
||||
begin_traversal();
|
||||
auto guard = scope_exit{[this] {
|
||||
|
||||
+7
-6
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <luma/detail/errors.hpp>
|
||||
|
||||
|
||||
auto luma::router::register_route(std::string name, std::function<std::expected<std::unique_ptr<luma::control>, modulus::error>()> factory) -> std::expected<void, modulus::error> {
|
||||
if (name.empty()) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, "route name must not be empty");
|
||||
@@ -25,7 +26,7 @@ auto luma::router::set_retire_handler(std::function<void(std::unique_ptr<luma::c
|
||||
retire_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
auto luma::router::replace(std::string const& name) -> std::expected<void, modulus::error> {
|
||||
auto luma::router::replace(std::string const &name) -> std::expected<void, modulus::error> {
|
||||
if (name.empty()) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, "route name must not be empty");
|
||||
}
|
||||
@@ -50,7 +51,7 @@ auto luma::router::replace(std::string const& name) -> std::expected<void, modul
|
||||
return {};
|
||||
}
|
||||
|
||||
auto luma::router::push(std::string const& name) -> std::expected<void, modulus::error> {
|
||||
auto luma::router::push(std::string const &name) -> std::expected<void, modulus::error> {
|
||||
if (name.empty()) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, "route name must not be empty");
|
||||
}
|
||||
@@ -90,7 +91,7 @@ auto luma::router::clear_stack() -> void {
|
||||
retire_stack();
|
||||
}
|
||||
|
||||
auto luma::router::current() -> luma::control* {
|
||||
auto luma::router::current() -> luma::control * {
|
||||
if (stack_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -98,7 +99,7 @@ auto luma::router::current() -> luma::control* {
|
||||
return stack_.back().view.get();
|
||||
}
|
||||
|
||||
auto luma::router::current() const -> luma::control const* {
|
||||
auto luma::router::current() const -> luma::control const * {
|
||||
if (stack_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -106,7 +107,7 @@ auto luma::router::current() const -> luma::control const* {
|
||||
return stack_.back().view.get();
|
||||
}
|
||||
|
||||
auto luma::router::current_route() const -> std::string const& {
|
||||
auto luma::router::current_route() const -> std::string const & {
|
||||
if (stack_.empty()) {
|
||||
return empty_route_;
|
||||
}
|
||||
@@ -125,7 +126,7 @@ auto luma::router::retire(std::unique_ptr<luma::control> view) -> void {
|
||||
auto luma::router::retire_stack() -> void {
|
||||
auto removed = std::vector<luma::router::entry>{};
|
||||
removed.swap(stack_);
|
||||
for (auto& entry : removed) {
|
||||
for (auto &entry : removed) {
|
||||
retire(std::move(entry.view));
|
||||
}
|
||||
}
|
||||
|
||||
+53
-50
@@ -16,13 +16,13 @@
|
||||
|
||||
#include <luma/detail/errors.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto json_options = modulus::json_config::options{
|
||||
.category = "luma",
|
||||
.invalid_argument_code = luma::detail::error_invalid_argument,
|
||||
.not_found_code = luma::detail::error_not_found
|
||||
};
|
||||
.not_found_code = luma::detail::error_not_found};
|
||||
|
||||
auto invalid_color(std::string message) -> std::expected<luma::color, modulus::error> {
|
||||
assert(!message.empty());
|
||||
@@ -39,18 +39,18 @@ auto invalid_status(std::string message) -> std::expected<void, modulus::error>
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, std::move(message));
|
||||
}
|
||||
|
||||
auto byte_value(boost::json::value const& value, std::char_t const* name) -> std::expected<std::uint8_t, modulus::error> {
|
||||
auto byte_value(boost::json::value const &value, std::char_t const *name) -> std::expected<std::uint8_t, modulus::error> {
|
||||
assert(name != nullptr);
|
||||
auto const* integer = value.if_int64();
|
||||
auto const *integer = value.if_int64();
|
||||
if (integer == nullptr || *integer < 0 || *integer > 255) {
|
||||
return luma::detail::fail<std::uint8_t>(luma::detail::error_invalid_argument, std::string{name} + " must be an integer from 0 to 255");
|
||||
}
|
||||
return modulus::success(static_cast<std::uint8_t>(*integer));
|
||||
}
|
||||
|
||||
auto apply_color(boost::json::object const& object, std::char_t const* name, luma::color& target) -> std::expected<void, modulus::error> {
|
||||
auto apply_color(boost::json::object const &object, std::char_t const *name, luma::color &target) -> std::expected<void, modulus::error> {
|
||||
assert(name != nullptr);
|
||||
if (auto const* value = object.if_contains(name)) {
|
||||
if (auto const *value = object.if_contains(name)) {
|
||||
auto parsed = luma::parse_color(*value);
|
||||
if (!parsed) {
|
||||
return modulus::fail<void>(parsed.error());
|
||||
@@ -60,38 +60,39 @@ auto apply_color(boost::json::object const& object, std::char_t const* name, lum
|
||||
return {};
|
||||
}
|
||||
|
||||
auto number_value(boost::json::value const& value) -> std::optional<std::float64_t>;
|
||||
auto number_value(boost::json::value const &value) -> std::optional<std::float64_t>;
|
||||
|
||||
auto apply_text_style(boost::json::object const& object, std::char_t const* name, luma::text_style& target) -> std::expected<void, modulus::error> {
|
||||
auto apply_text_style(boost::json::object const &object, std::char_t const *name, luma::text_style &target) -> std::expected<void, modulus::error> {
|
||||
assert(name != nullptr);
|
||||
if (auto const* value = object.if_contains(name)) {
|
||||
if (auto const *value = object.if_contains(name)) {
|
||||
auto style = modulus::json_config::require_object(*value, name, json_options);
|
||||
if (!style) {
|
||||
return modulus::fail<void>(style.error());
|
||||
}
|
||||
auto unknown = modulus::json_config::reject_unknown_fields(**style, {
|
||||
"font",
|
||||
"size",
|
||||
"line_height",
|
||||
}, name, json_options);
|
||||
"font",
|
||||
"size",
|
||||
"line_height",
|
||||
},
|
||||
name, json_options);
|
||||
if (!unknown) {
|
||||
return modulus::fail<void>(unknown.error());
|
||||
}
|
||||
if (auto const* font = (*style)->if_contains("font")) {
|
||||
auto const* text = font->if_string();
|
||||
if (auto const *font = (*style)->if_contains("font")) {
|
||||
auto const *text = font->if_string();
|
||||
if (text == nullptr) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, std::string{name} + ".font must be a string");
|
||||
}
|
||||
target.font = std::string{text->c_str()};
|
||||
}
|
||||
if (auto const* size = (*style)->if_contains("size")) {
|
||||
if (auto const *size = (*style)->if_contains("size")) {
|
||||
auto number = number_value(*size);
|
||||
if (!number || *number <= 0.0) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, std::string{name} + ".size must be positive");
|
||||
}
|
||||
target.size = static_cast<std::float32_t>(*number);
|
||||
}
|
||||
if (auto const* line_height = (*style)->if_contains("line_height")) {
|
||||
if (auto const *line_height = (*style)->if_contains("line_height")) {
|
||||
auto number = number_value(*line_height);
|
||||
if (!number || *number <= 0.0) {
|
||||
return luma::detail::fail<void>(luma::detail::error_invalid_argument, std::string{name} + ".line_height must be positive");
|
||||
@@ -102,11 +103,11 @@ auto apply_text_style(boost::json::object const& object, std::char_t const* name
|
||||
return {};
|
||||
}
|
||||
|
||||
auto number_value(boost::json::value const& value) -> std::optional<std::float64_t> {
|
||||
if (auto const* number = value.if_double()) {
|
||||
auto number_value(boost::json::value const &value) -> std::optional<std::float64_t> {
|
||||
if (auto const *number = value.if_double()) {
|
||||
return *number;
|
||||
}
|
||||
if (auto const* integer = value.if_int64()) {
|
||||
if (auto const *integer = value.if_int64()) {
|
||||
return static_cast<std::float64_t>(*integer);
|
||||
}
|
||||
return std::nullopt;
|
||||
@@ -118,7 +119,7 @@ auto luma::default_theme() -> luma::theme {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto luma::validate(luma::text_style const& style) -> std::expected<void, modulus::error> {
|
||||
auto luma::validate(luma::text_style const &style) -> std::expected<void, modulus::error> {
|
||||
if (style.font.empty()) {
|
||||
return invalid_status("text style font must not be empty");
|
||||
}
|
||||
@@ -131,7 +132,7 @@ auto luma::validate(luma::text_style const& style) -> std::expected<void, modulu
|
||||
return {};
|
||||
}
|
||||
|
||||
auto luma::validate(luma::theme const& theme) -> std::expected<void, modulus::error> {
|
||||
auto luma::validate(luma::theme const &theme) -> std::expected<void, modulus::error> {
|
||||
if (auto status = luma::validate(theme.body); !status) {
|
||||
return modulus::fail<void>(status.error());
|
||||
}
|
||||
@@ -147,23 +148,24 @@ auto luma::validate(luma::theme const& theme) -> std::expected<void, modulus::er
|
||||
return {};
|
||||
}
|
||||
|
||||
auto luma::parse_color(boost::json::value const& value) -> std::expected<luma::color, modulus::error> {
|
||||
auto luma::parse_color(boost::json::value const &value) -> std::expected<luma::color, modulus::error> {
|
||||
auto object = modulus::json_config::require_object(value, "color", json_options);
|
||||
if (!object) {
|
||||
return invalid_color(std::string{object.error().message()});
|
||||
}
|
||||
auto unknown = modulus::json_config::reject_unknown_fields(**object, {
|
||||
"r",
|
||||
"g",
|
||||
"b",
|
||||
"a",
|
||||
}, "color", json_options);
|
||||
"r",
|
||||
"g",
|
||||
"b",
|
||||
"a",
|
||||
},
|
||||
"color", json_options);
|
||||
if (!unknown) {
|
||||
return invalid_color(std::string{unknown.error().message()});
|
||||
}
|
||||
auto const* red_value = (*object)->if_contains("r");
|
||||
auto const* green_value = (*object)->if_contains("g");
|
||||
auto const* blue_value = (*object)->if_contains("b");
|
||||
auto const *red_value = (*object)->if_contains("r");
|
||||
auto const *green_value = (*object)->if_contains("g");
|
||||
auto const *blue_value = (*object)->if_contains("b");
|
||||
if (red_value == nullptr || green_value == nullptr || blue_value == nullptr) {
|
||||
return invalid_color("color requires r, g, and b fields");
|
||||
}
|
||||
@@ -180,7 +182,7 @@ auto luma::parse_color(boost::json::value const& value) -> std::expected<luma::c
|
||||
return invalid_color(std::string{blue.error().message()});
|
||||
}
|
||||
auto alpha = std::uint8_t{255};
|
||||
if (auto const* alpha_value = (*object)->if_contains("a")) {
|
||||
if (auto const *alpha_value = (*object)->if_contains("a")) {
|
||||
auto parsed = byte_value(*alpha_value, "a");
|
||||
if (!parsed) {
|
||||
return invalid_color(std::string{parsed.error().message()});
|
||||
@@ -195,24 +197,25 @@ auto luma::parse_color(boost::json::value const& value) -> std::expected<luma::c
|
||||
});
|
||||
}
|
||||
|
||||
auto luma::parse_theme(boost::json::value const& value) -> std::expected<luma::theme, modulus::error> {
|
||||
auto luma::parse_theme(boost::json::value const &value) -> std::expected<luma::theme, modulus::error> {
|
||||
auto object = modulus::json_config::require_object(value, "theme", json_options);
|
||||
if (!object) {
|
||||
return invalid_theme(std::string{object.error().message()});
|
||||
}
|
||||
auto unknown = modulus::json_config::reject_unknown_fields(**object, {
|
||||
"background",
|
||||
"surface",
|
||||
"surface_active",
|
||||
"text",
|
||||
"text_muted",
|
||||
"border",
|
||||
"accent",
|
||||
"body",
|
||||
"title",
|
||||
"radius",
|
||||
"spacing",
|
||||
}, "theme", json_options);
|
||||
"background",
|
||||
"surface",
|
||||
"surface_active",
|
||||
"text",
|
||||
"text_muted",
|
||||
"border",
|
||||
"accent",
|
||||
"body",
|
||||
"title",
|
||||
"radius",
|
||||
"spacing",
|
||||
},
|
||||
"theme", json_options);
|
||||
if (!unknown) {
|
||||
return invalid_theme(std::string{unknown.error().message()});
|
||||
}
|
||||
@@ -244,14 +247,14 @@ auto luma::parse_theme(boost::json::value const& value) -> std::expected<luma::t
|
||||
if (auto status = apply_text_style(**object, "title", parsed.title); !status) {
|
||||
return invalid_theme(std::string{status.error().message()});
|
||||
}
|
||||
if (auto const* radius = (*object)->if_contains("radius")) {
|
||||
if (auto const *radius = (*object)->if_contains("radius")) {
|
||||
auto number = number_value(*radius);
|
||||
if (!number || *number < 0.0) {
|
||||
return invalid_theme("theme.radius must be non-negative");
|
||||
}
|
||||
parsed.radius = static_cast<std::float32_t>(*number);
|
||||
}
|
||||
if (auto const* spacing = (*object)->if_contains("spacing")) {
|
||||
if (auto const *spacing = (*object)->if_contains("spacing")) {
|
||||
auto number = number_value(*spacing);
|
||||
if (!number || *number < 0.0) {
|
||||
return invalid_theme("theme.spacing must be non-negative");
|
||||
@@ -264,7 +267,7 @@ auto luma::parse_theme(boost::json::value const& value) -> std::expected<luma::t
|
||||
return modulus::success(parsed);
|
||||
}
|
||||
|
||||
auto luma::to_json(luma::color const& color) -> boost::json::value {
|
||||
auto luma::to_json(luma::color const &color) -> boost::json::value {
|
||||
return boost::json::object{
|
||||
{
|
||||
"r",
|
||||
@@ -285,7 +288,7 @@ auto luma::to_json(luma::color const& color) -> boost::json::value {
|
||||
};
|
||||
}
|
||||
|
||||
auto luma::to_json(luma::text_style const& style) -> boost::json::value {
|
||||
auto luma::to_json(luma::text_style const &style) -> boost::json::value {
|
||||
return boost::json::object{
|
||||
{
|
||||
"font",
|
||||
@@ -302,7 +305,7 @@ auto luma::to_json(luma::text_style const& style) -> boost::json::value {
|
||||
};
|
||||
}
|
||||
|
||||
auto luma::to_json(luma::theme const& theme) -> boost::json::value {
|
||||
auto luma::to_json(luma::theme const &theme) -> boost::json::value {
|
||||
return boost::json::object{
|
||||
{
|
||||
"background",
|
||||
|
||||
+47
-44
@@ -19,13 +19,14 @@
|
||||
#include <luma/render.hpp>
|
||||
#include <luma/theme.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class scope_exit {
|
||||
public:
|
||||
explicit scope_exit(std::function<void()> action);
|
||||
scope_exit(scope_exit const&) = delete;
|
||||
auto operator=(scope_exit const&) -> scope_exit& = delete;
|
||||
scope_exit(scope_exit const &) = delete;
|
||||
auto operator=(scope_exit const &) -> scope_exit & = delete;
|
||||
~scope_exit();
|
||||
private:
|
||||
std::function<void()> action_{};
|
||||
@@ -37,7 +38,7 @@ auto finite_rect(luma::rect value) -> std::bool_t {
|
||||
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.width) && std::isfinite(value.height) && value.width >= 0.0F && value.height >= 0.0F;
|
||||
}
|
||||
|
||||
auto title_height(luma::window_options const& options, std::float32_t line_height) -> std::float32_t {
|
||||
auto title_height(luma::window_options const &options, std::float32_t line_height) -> std::float32_t {
|
||||
if (!options.decorated) {
|
||||
return {};
|
||||
}
|
||||
@@ -45,7 +46,7 @@ auto title_height(luma::window_options const& options, std::float32_t line_heigh
|
||||
return std::min(desired, options.bounds.height);
|
||||
}
|
||||
|
||||
auto title_bounds(luma::window_options const& options, std::float32_t line_height) -> luma::rect {
|
||||
auto title_bounds(luma::window_options const &options, std::float32_t line_height) -> luma::rect {
|
||||
return {
|
||||
.x = options.bounds.x,
|
||||
.y = options.bounds.y,
|
||||
@@ -54,7 +55,7 @@ auto title_bounds(luma::window_options const& options, std::float32_t line_heigh
|
||||
};
|
||||
}
|
||||
|
||||
auto client_bounds(luma::window_options const& options, std::float32_t line_height) -> luma::rect {
|
||||
auto client_bounds(luma::window_options const &options, std::float32_t line_height) -> luma::rect {
|
||||
if (!options.decorated) {
|
||||
return options.bounds;
|
||||
}
|
||||
@@ -77,14 +78,15 @@ auto unavailable(std::string message) -> std::expected<VALUE, modulus::error> {
|
||||
return luma::detail::fail<VALUE>(luma::detail::error_unavailable, std::move(message));
|
||||
}
|
||||
|
||||
auto make_event(luma::touch_event const& event, luma::touch_point point) -> luma::touch_event {
|
||||
auto make_event(luma::touch_event const &event, luma::touch_point point) -> luma::touch_event {
|
||||
return {
|
||||
.points = {point},
|
||||
.time = event.time,
|
||||
};
|
||||
}
|
||||
|
||||
scope_exit::scope_exit(std::function<void()> action) : action_{std::move(action)} {}
|
||||
scope_exit::scope_exit(std::function<void()> action)
|
||||
: action_{std::move(action)} {}
|
||||
|
||||
scope_exit::~scope_exit() {
|
||||
if (action_) {
|
||||
@@ -94,7 +96,8 @@ scope_exit::~scope_exit() {
|
||||
|
||||
}
|
||||
|
||||
luma::window::window(std::uint64_t id, luma::window_options options, std::unique_ptr<luma::control> content) : id_{id}, options_{std::move(options)}, content_{std::move(content)} {
|
||||
luma::window::window(std::uint64_t id, luma::window_options options, std::unique_ptr<luma::control> content)
|
||||
: id_{id}, options_{std::move(options)}, content_{std::move(content)} {
|
||||
assert(id_ != 0);
|
||||
assert(finite_rect(options_.bounds));
|
||||
assert(content_ != nullptr);
|
||||
@@ -105,7 +108,7 @@ auto luma::window::id() const -> std::uint64_t {
|
||||
return id_;
|
||||
}
|
||||
|
||||
auto luma::window::options() const -> luma::window_options const& {
|
||||
auto luma::window::options() const -> luma::window_options const & {
|
||||
assert(finite_rect(options_.bounds));
|
||||
return options_;
|
||||
}
|
||||
@@ -126,12 +129,12 @@ auto luma::window::visible() const -> std::bool_t {
|
||||
return options_.visible;
|
||||
}
|
||||
|
||||
auto luma::window::content() -> luma::control* {
|
||||
auto luma::window::content() -> luma::control * {
|
||||
assert(content_ != nullptr);
|
||||
return content_.get();
|
||||
}
|
||||
|
||||
auto luma::window::content() const -> luma::control const* {
|
||||
auto luma::window::content() const -> luma::control const * {
|
||||
assert(content_ != nullptr);
|
||||
return content_.get();
|
||||
}
|
||||
@@ -246,11 +249,11 @@ auto luma::window_manager::set_visible(std::uint64_t id, std::bool_t visible) ->
|
||||
return {};
|
||||
}
|
||||
|
||||
auto luma::window_manager::find(std::uint64_t id) -> luma::window* {
|
||||
auto luma::window_manager::find(std::uint64_t id) -> luma::window * {
|
||||
if (id == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
auto found = std::find_if(windows_.begin(), windows_.end(), [id](auto const& window) { return window && window->id() == id; });
|
||||
auto found = std::find_if(windows_.begin(), windows_.end(), [id](auto const &window) { return window && window->id() == id; });
|
||||
return found == windows_.end() ? nullptr : found->get();
|
||||
}
|
||||
|
||||
@@ -265,7 +268,7 @@ auto luma::window_manager::clear() -> void {
|
||||
active_touches_.clear();
|
||||
}
|
||||
|
||||
auto luma::window_manager::measure(luma::draw_context& context, luma::theme const& theme, luma::constraints limits) -> luma::size {
|
||||
auto luma::window_manager::measure(luma::draw_context &context, luma::theme const &theme, luma::constraints limits) -> luma::size {
|
||||
title_line_height_ = theme.body.line_height;
|
||||
++mutation_depth_;
|
||||
auto guard = scope_exit{[this] {
|
||||
@@ -273,7 +276,7 @@ auto luma::window_manager::measure(luma::draw_context& context, luma::theme cons
|
||||
--mutation_depth_;
|
||||
apply_pending_operations();
|
||||
}};
|
||||
for (auto& window : windows_) {
|
||||
for (auto &window : windows_) {
|
||||
assert(window != nullptr);
|
||||
if (!window->visible()) {
|
||||
continue;
|
||||
@@ -281,12 +284,12 @@ auto luma::window_manager::measure(luma::draw_context& context, luma::theme cons
|
||||
assert(window->content() != nullptr);
|
||||
auto client = client_bounds(window->options(), title_line_height_);
|
||||
window->content()->measure(context, theme, {
|
||||
.min = {},
|
||||
.max = {
|
||||
.width = client.width,
|
||||
.height = client.height,
|
||||
},
|
||||
});
|
||||
.min = {},
|
||||
.max = {
|
||||
.width = client.width,
|
||||
.height = client.height,
|
||||
},
|
||||
});
|
||||
}
|
||||
return luma::clamp(limits.max, limits);
|
||||
}
|
||||
@@ -300,7 +303,7 @@ auto luma::window_manager::arrange(luma::rect bounds) -> void {
|
||||
apply_pending_operations();
|
||||
}};
|
||||
luma::control::arrange(bounds);
|
||||
for (auto& window : windows_) {
|
||||
for (auto &window : windows_) {
|
||||
assert(window != nullptr);
|
||||
if (window->visible()) {
|
||||
assert(window->content() != nullptr);
|
||||
@@ -309,7 +312,7 @@ auto luma::window_manager::arrange(luma::rect bounds) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto luma::window_manager::draw(luma::draw_context& context, luma::theme const& theme) const -> void {
|
||||
auto luma::window_manager::draw(luma::draw_context &context, luma::theme const &theme) const -> void {
|
||||
title_line_height_ = theme.body.line_height;
|
||||
++mutation_depth_;
|
||||
auto guard = scope_exit{[this] {
|
||||
@@ -317,7 +320,7 @@ auto luma::window_manager::draw(luma::draw_context& context, luma::theme const&
|
||||
--mutation_depth_;
|
||||
apply_pending_operations();
|
||||
}};
|
||||
for (auto const& window : windows_) {
|
||||
for (auto const &window : windows_) {
|
||||
assert(window != nullptr);
|
||||
if (!window->visible()) {
|
||||
continue;
|
||||
@@ -333,15 +336,16 @@ auto luma::window_manager::draw(luma::draw_context& context, luma::theme const&
|
||||
auto title = title_bounds(window->options(), title_line_height_);
|
||||
context.fill_rect(title, theme.surface_active);
|
||||
context.draw_text(window->options().title, {
|
||||
.x = title.x + 8.0F,
|
||||
.y = title.y + window_title_vertical_padding,
|
||||
}, theme.body, theme.text);
|
||||
.x = title.x + 8.0F,
|
||||
.y = title.y + window_title_vertical_padding,
|
||||
},
|
||||
theme.body, theme.text);
|
||||
}
|
||||
context.stroke_rect(bounds, 1.0F, theme.border);
|
||||
}
|
||||
}
|
||||
|
||||
auto luma::window_manager::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto luma::window_manager::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
if (event.points.empty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -352,10 +356,10 @@ auto luma::window_manager::handle_touch(luma::touch_event const& event) -> std::
|
||||
apply_pending_operations();
|
||||
}};
|
||||
auto handled = false;
|
||||
for (auto const& point : event.points) {
|
||||
for (auto const &point : event.points) {
|
||||
if (auto captured = active_touches_.find(point.id); captured != active_touches_.end()) {
|
||||
auto const window_id = captured->second;
|
||||
auto* captured_window = find(window_id);
|
||||
auto *captured_window = find(window_id);
|
||||
auto const captured_visible = captured_window != nullptr && captured_window->visible();
|
||||
if (captured_visible) {
|
||||
assert(captured_window->content() != nullptr);
|
||||
@@ -368,7 +372,6 @@ auto luma::window_manager::handle_touch(luma::touch_event const& event) -> std::
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto current = windows_.rbegin(); current != windows_.rend(); ++current) {
|
||||
assert(*current != nullptr);
|
||||
if (!(*current)->visible()) {
|
||||
@@ -400,8 +403,8 @@ auto luma::window_manager::contains_effective(std::uint64_t id) const -> std::bo
|
||||
if (id == 0) {
|
||||
return false;
|
||||
}
|
||||
auto exists = std::find_if(windows_.begin(), windows_.end(), [id](auto const& window) { return window && window->id() == id; }) != windows_.end();
|
||||
for (auto const& operation : pending_operations_) {
|
||||
auto exists = std::find_if(windows_.begin(), windows_.end(), [id](auto const &window) { return window && window->id() == id; }) != windows_.end();
|
||||
for (auto const &operation : pending_operations_) {
|
||||
switch (operation.kind) {
|
||||
case luma::window_manager::pending_kind::OPEN:
|
||||
if (operation.id == id) {
|
||||
@@ -426,7 +429,7 @@ auto luma::window_manager::contains_effective(std::uint64_t id) const -> std::bo
|
||||
}
|
||||
|
||||
auto luma::window_manager::close_now(std::uint64_t id) const -> void {
|
||||
auto found = std::find_if(windows_.begin(), windows_.end(), [id](auto const& window) { return window && window->id() == id; });
|
||||
auto found = std::find_if(windows_.begin(), windows_.end(), [id](auto const &window) { return window && window->id() == id; });
|
||||
if (found == windows_.end()) {
|
||||
return;
|
||||
}
|
||||
@@ -442,7 +445,7 @@ auto luma::window_manager::close_now(std::uint64_t id) const -> void {
|
||||
}
|
||||
|
||||
auto luma::window_manager::bring_to_front_now(std::uint64_t id) const -> void {
|
||||
auto found = std::find_if(windows_.begin(), windows_.end(), [id](auto const& window) { return window && window->id() == id; });
|
||||
auto found = std::find_if(windows_.begin(), windows_.end(), [id](auto const &window) { return window && window->id() == id; });
|
||||
if (found == windows_.end()) {
|
||||
return;
|
||||
}
|
||||
@@ -452,14 +455,14 @@ auto luma::window_manager::bring_to_front_now(std::uint64_t id) const -> void {
|
||||
}
|
||||
|
||||
auto luma::window_manager::set_bounds_now(std::uint64_t id, luma::rect bounds) const -> void {
|
||||
auto* window = const_cast<luma::window_manager*>(this)->find(id);
|
||||
auto *window = const_cast<luma::window_manager *>(this)->find(id);
|
||||
if (window != nullptr) {
|
||||
static_cast<void>(window->set_bounds(bounds));
|
||||
}
|
||||
}
|
||||
|
||||
auto luma::window_manager::set_visible_now(std::uint64_t id, std::bool_t visible) const -> void {
|
||||
auto* window = const_cast<luma::window_manager*>(this)->find(id);
|
||||
auto *window = const_cast<luma::window_manager *>(this)->find(id);
|
||||
if (window != nullptr) {
|
||||
window->set_visible(visible);
|
||||
}
|
||||
@@ -471,7 +474,7 @@ auto luma::window_manager::apply_pending_operations() const -> void {
|
||||
}
|
||||
auto pending = std::vector<luma::window_manager::pending_operation>{};
|
||||
pending.swap(pending_operations_);
|
||||
for (auto& operation : pending) {
|
||||
for (auto &operation : pending) {
|
||||
switch (operation.kind) {
|
||||
case luma::window_manager::pending_kind::OPEN:
|
||||
if (operation.window) {
|
||||
@@ -505,20 +508,20 @@ auto luma::window_manager::invalidate_layout() -> void {
|
||||
--mutation_depth_;
|
||||
apply_pending_operations();
|
||||
}};
|
||||
auto controls = std::vector<luma::control*>{};
|
||||
auto controls = std::vector<luma::control *>{};
|
||||
controls.reserve(windows_.size() + pending_operations_.size());
|
||||
for (auto& window : windows_) {
|
||||
for (auto &window : windows_) {
|
||||
assert(window != nullptr);
|
||||
assert(window->content() != nullptr);
|
||||
controls.push_back(window->content());
|
||||
}
|
||||
for (auto& operation : pending_operations_) {
|
||||
for (auto &operation : pending_operations_) {
|
||||
if (operation.window) {
|
||||
assert(operation.window->content() != nullptr);
|
||||
controls.push_back(operation.window->content());
|
||||
}
|
||||
}
|
||||
for (auto* control : controls) {
|
||||
for (auto *control : controls) {
|
||||
assert(control != nullptr);
|
||||
control->invalidate_layout();
|
||||
}
|
||||
@@ -526,12 +529,12 @@ auto luma::window_manager::invalidate_layout() -> void {
|
||||
|
||||
auto luma::window_manager::bind_layout_invalidator(std::function<void()> invalidator) -> void {
|
||||
luma::control::bind_layout_invalidator(invalidator);
|
||||
for (auto& window : windows_) {
|
||||
for (auto &window : windows_) {
|
||||
assert(window != nullptr);
|
||||
assert(window->content() != nullptr);
|
||||
window->content()->bind_layout_invalidator(invalidator);
|
||||
}
|
||||
for (auto& operation : pending_operations_) {
|
||||
for (auto &operation : pending_operations_) {
|
||||
if (operation.window) {
|
||||
assert(operation.window->content() != nullptr);
|
||||
operation.window->content()->bind_layout_invalidator(invalidator);
|
||||
|
||||
Reference in New Issue
Block a user