MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ColorPicker.cpp
Go to the documentation of this file.
1#include "ColorPicker.hpp"
2#include "Geometry.hpp"
3
9
11
12namespace {
13
14 constexpr uint32_t k_square_cells = 12;
15 constexpr uint32_t k_hue_segments = 6;
16 constexpr float k_marker_half_height = 0.018F;
17
18 void append_quad(
19 std::vector<Kakshya::MeshVertex>& out,
20 Kinesis::AABB2D region,
21 glm::vec3 bl, glm::vec3 br, glm::vec3 tl, glm::vec3 tr)
22 {
23 using V = Kakshya::MeshVertex;
24 const V vbl { .position = { region.min.x, region.min.y, 0.F }, .color = bl };
25 const V vbr { .position = { region.max.x, region.min.y, 0.F }, .color = br };
26 const V vtl { .position = { region.min.x, region.max.y, 0.F }, .color = tl };
27 const V vtr { .position = { region.max.x, region.max.y, 0.F }, .color = tr };
28 out.insert(out.end(), { vbl, vbr, vtl, vbr, vtr, vtl });
29 }
30
31 void append_quad(std::vector<Kakshya::MeshVertex>& out, Kinesis::AABB2D region, glm::vec3 color)
32 {
33 append_quad(out, region, color, color, color, color);
34 }
35
36 void append_marker(std::vector<Kakshya::MeshVertex>& out, glm::vec2 centre, glm::vec2 half)
37 {
38 append_quad(out, { .min = centre - half, .max = centre + half }, glm::vec3(0.F));
39 append_quad(out, { .min = centre - half * 0.6F, .max = centre + half * 0.6F }, glm::vec3(1.F));
40 }
41
42}
43
45 Surface& surface,
46 LayoutCursor& cursor,
47 float x_min, float x_max, float height)
48{
49 auto& ctx = surface.ctx();
50 const auto layer_ptr = surface.layer_ptr();
51
52 const Kinesis::AABB2D advanced = cursor.advance(height);
53 const float width = x_max - x_min;
54 const float y_min = advanced.min.y;
55 const float y_max = advanced.max.y;
56
57 const Kinesis::AABB2D whole { .min = { x_min, y_min }, .max = { x_max, y_max } };
58 const Kinesis::AABB2D square_rect { .min = { x_min, y_min }, .max = { x_min + width * 0.62F, y_max } };
59 const Kinesis::AABB2D hue_rect { .min = { x_min + width * 0.66F, y_min }, .max = { x_min + width * 0.78F, y_max } };
60 const Kinesis::AABB2D swatch_rect { .min = { x_min + width * 0.82F, y_min }, .max = { x_max, y_max } };
61
62 GeometryFn<glm::vec3> geometry = [square_rect, hue_rect, swatch_rect, window = std::weak_ptr<Core::Window>(surface.window())](
63 glm::vec3 value, std::vector<uint8_t>& out, Element&) {
64 float aspect = 1.F;
65 if (const auto locked = window.lock()) {
66 const auto& state = locked->get_state();
67 if (state.current_width > 0)
68 aspect = static_cast<float>(state.current_height) / static_cast<float>(state.current_width);
69 }
70 const glm::vec2 marker { k_marker_half_height * aspect, k_marker_half_height };
71
72 std::vector<Kakshya::MeshVertex> verts;
73 verts.reserve(static_cast<size_t>(k_square_cells * k_square_cells + k_hue_segments + 6) * 6);
74
75 append_quad(verts, swatch_rect, Kinesis::hsv_to_rgb(value));
76
77 const float cells = static_cast<float>(k_square_cells);
78 for (uint32_t i = 0; i < k_square_cells; ++i) {
79 for (uint32_t j = 0; j < k_square_cells; ++j) {
80 const float s0 = static_cast<float>(i) / cells;
81 const float s1 = static_cast<float>(i + 1) / cells;
82 const float v0 = static_cast<float>(j) / cells;
83 const float v1 = static_cast<float>(j + 1) / cells;
84 const Kinesis::AABB2D cell {
85 .min = { square_rect.min.x + s0 * square_rect.width(), square_rect.min.y + v0 * square_rect.height() },
86 .max = { square_rect.min.x + s1 * square_rect.width(), square_rect.min.y + v1 * square_rect.height() },
87 };
88 append_quad(verts, cell,
89 Kinesis::hsv_to_rgb({ value.x, s0, v0 }),
90 Kinesis::hsv_to_rgb({ value.x, s1, v0 }),
91 Kinesis::hsv_to_rgb({ value.x, s0, v1 }),
92 Kinesis::hsv_to_rgb({ value.x, s1, v1 }));
93 }
94 }
95
96 const float segments = static_cast<float>(k_hue_segments);
97 for (uint32_t k = 0; k < k_hue_segments; ++k) {
98 const float h0 = static_cast<float>(k) / segments;
99 const float h1 = static_cast<float>(k + 1) / segments;
100 const Kinesis::AABB2D segment {
101 .min = { hue_rect.min.x, hue_rect.min.y + h0 * hue_rect.height() },
102 .max = { hue_rect.max.x, hue_rect.min.y + h1 * hue_rect.height() },
103 };
104 const glm::vec3 low = Kinesis::hsv_to_rgb({ h0, 1.F, 1.F });
105 const glm::vec3 high = Kinesis::hsv_to_rgb({ h1, 1.F, 1.F });
106 append_quad(verts, segment, low, low, high, high);
107 }
108
109 const float s = std::clamp(value.y, 0.F, 1.F);
110 const float v = std::clamp(value.z, 0.F, 1.F);
111 append_marker(verts,
112 { square_rect.min.x + s * square_rect.width(), square_rect.min.y + v * square_rect.height() },
113 marker);
114
115 const float hue_y = hue_rect.min.y + std::clamp(value.x, 0.F, 1.F) * hue_rect.height();
116 const glm::vec2 hue_half { hue_rect.width() * 0.5F + marker.x, marker.y * 0.5F };
117 append_marker(verts, { hue_rect.min.x + hue_rect.width() * 0.5F, hue_y }, hue_half);
118
120 };
121
122 const glm::vec3 initial_hsv = Kinesis::rgb_to_hsv(m_initial);
123 auto drawn = internal::atelier().create_element<glm::vec3>(
124 surface, std::move(geometry), initial_hsv, Graphics::PrimitiveTopology::TRIANGLE_LIST);
125
126 auto color_state = std::make_shared<MappedState<glm::vec3>>();
127 color_state->write(m_initial);
128
129 const uint32_t square_id = surface.add(Element {}.with_name("color_square").with_bounds(square_rect));
130 const uint32_t hue_id = surface.add(Element {}.with_name("color_hue").with_bounds(hue_rect));
131 layer_ptr->relate(drawn.element.id, square_id);
132 layer_ptr->relate(drawn.element.id, hue_id);
133
134 auto commit = [hsv_state = drawn.state, color_state, callback = m_on_change](glm::vec3 next) {
135 hsv_state->write(next);
136 const glm::vec3 rgb = Kinesis::hsv_to_rgb(next);
137 color_state->write(rgb);
138 if (callback)
139 callback(rgb);
140 };
141
142 const auto square_fraction = Kinesis::unit_square(square_rect);
143 auto pick_square = [commit, hsv_state = drawn.state, square_fraction](glm::vec2 ndc) {
144 const glm::vec2 fraction = square_fraction(ndc);
145 commit({ hsv_state->value.x, fraction.x, fraction.y });
146 };
147 auto pick_hue = [commit, hsv_state = drawn.state, hue_rect](glm::vec2 ndc) {
148 if (hue_rect.height() <= 0.F)
149 return;
150 const float hue = std::clamp((ndc.y - hue_rect.min.y) / hue_rect.height(), 0.F, 1.F);
151 commit({ hue, hsv_state->value.y, hsv_state->value.z });
152 };
153
154 ctx.on_press(square_id, IO::MouseButtons::Left,
155 [pick_square](uint32_t, glm::vec2 ndc) { pick_square(ndc); });
156 ctx.wire_drag(square_id, pick_square, IO::MouseButtons::Left);
157 ctx.on_press(hue_id, IO::MouseButtons::Left,
158 [pick_hue](uint32_t, glm::vec2 ndc) { pick_hue(ndc); });
159 ctx.wire_drag(hue_id, pick_hue, IO::MouseButtons::Left);
160
162 ElementRef { layer_ptr, square_id }, color_state, nullptr);
163
164 picker_id = drawn.element.id;
165 color = std::move(color_state);
166 hsv = drawn.state;
167 picker_bounds = whole;
168 cursor_out = cursor;
169 return *this;
170}
171
173 Surface& surface,
174 LayoutCursor& cursor,
175 float height)
176{
177 return place(surface, cursor, cursor.x_min(), cursor.x_max(), height);
178}
179
180} // namespace MayaFlux::Portal::Forma
glm::vec3 color
Definition Decoder.cpp:97
Illustrative geometry functions for common Mapped use cases.
PickCallback callback
Definition Picker.cpp:22
std::weak_ptr< Core::Window > window
Definition Picker.cpp:23
NDC-to-value mappings, inverse to the forward placement performed by geometry functions.
std::vector< float > * out
glm::vec3 value
uint32_t width
uint32_t height
void register_element(const ElementRef &ref, std::shared_ptr< MappedState< T > > state, std::shared_ptr< Buffers::FormaBuffer > buffer, std::function< float(T)> project={})
Register a MappedState<T> so that MappedState overloads and outbound bindings can resolve to the corr...
Definition Bridge.hpp:378
Kinesis::AABB2D advance(float height)
Advance the cursor downward by height and return the NDC AABB occupied by the primitive just placed,...
Reactive Y-position accumulator for vertical primitive stacking.
const std::shared_ptr< Layer > & layer_ptr() const noexcept
Shared handles for callers that need to keep components alive independently of the Surface (backgroun...
Definition Surface.hpp:188
const std::shared_ptr< Core::Window > & window() const noexcept
Access the rendering target window.
Definition Surface.hpp:178
Context & ctx() noexcept
Access the event router.
Definition Surface.hpp:170
Layer::Slot add(Element element)
Forward an Element to layer().add() and return the Slot.
Definition Surface.hpp:210
Named owner of a (Window, Layer, Context) triple: the Forma canvas.
Definition Surface.hpp:121
Mapped< T > create_element(Surface &surface, GeometryFn< T > geom, T initial, Graphics::PrimitiveTopology topology=Graphics::PrimitiveTopology::TRIANGLE_STRIP, std::function< float(T)> project={})
create_element on a buffer of its own, as a slice of topology, followed by one sync so bounds_hint an...
Definition Atelier.hpp:165
Bridge & bridge()
Bridge instance constructed during initialize.
Definition Atelier.cpp:80
glm::vec3 rgb_to_hsv(glm::vec3 rgb) noexcept
Convert red, green and blue to hue, saturation and value.
glm::vec3 hsv_to_rgb(glm::vec3 hsv) noexcept
Convert hue, saturation and value to red, green and blue.
std::function< glm::vec2(glm::vec2)> unit_square(AABB2D bounds, const std::function< glm::vec2()> &live_offset={}) noexcept
Unit-square coordinates of a point within bounds.
void write_verts(std::vector< uint8_t > &out, const std::vector< V > &verts)
Write a vertex array into a GeometryFn output buffer.
Definition Geometry.hpp:35
Atelier & atelier()
Accessor for the process-wide Atelier.
Definition Atelier.hpp:304
std::pair< std::weak_ptr< Layer >, uint32_t > ElementRef
Layer-scoped element identity.
Definition Bridge.hpp:34
std::function< void(T value, std::vector< uint8_t > &out_bytes, Element &element)> GeometryFn
Geometry function signature.
Definition Mapped.hpp:71
Axis-aligned bounding rectangle in a 2D coordinate space.
Definition Bounds.hpp:63
uint32_t picker_id
Element id of the drawn picker. Valid after place().
LayoutCursor cursor_out
Cursor positioned below the picker. Valid after place().
MAYAFLUX_API ColorPicker & place(Surface &surface, LayoutCursor &cursor, float x_min, float x_max, float height)
Register the picker and wire its input.
std::shared_ptr< MappedState< glm::vec3 > > hsv
Chosen colour as hue, saturation and value. Valid after place().
glm::vec3 m_initial
Colour shown before any interaction, as red, green and blue. Default: red.
std::function< void(glm::vec3)> m_on_change
Called with the new red, green and blue on every pick.
std::shared_ptr< MappedState< glm::vec3 > > color
Chosen colour as red, green and blue. Valid after place().
Kinesis::AABB2D picker_bounds
NDC region occupied by the picker. Valid after place().
A saturation and value square, a hue strip and a swatch, drawn into one buffer.
Element & with_bounds(Kinesis::AABB2D b)
Set the fast-reject AABB.
Definition Element.hpp:109
Element & with_name(std::string n)
Set the human-readable name used in Lila introspection and debug output.
Definition Element.hpp:304
A bounded, renderable region on a window surface.
Definition Element.hpp:61