10 void join_strip(std::vector<Kakshya::MeshVertex>& strip,
const std::vector<Kakshya::MeshVertex>& quad)
12 strip.push_back(strip.back());
13 strip.push_back(quad.front());
14 strip.insert(strip.end(), quad.begin(), quad.end());
21 glm::vec3 track_color,
22 glm::vec3 handle_color,
23 const std::function<glm::vec2()>& live_offset)
25 GeometryFn<float> fn = [bounds, handle_w, track_color, handle_color, live_offset](
26 float v, std::vector<uint8_t>&
out,
Element& el) {
27 const glm::vec2 shift = live_offset ? live_offset() : glm::vec2(0.F);
30 float x = live.
min.x + v * (live.
width() - handle_w);
31 float yt = live.
min.y + live.
height() * 0.35F;
32 float yb = live.
min.y + live.
height() * 0.65F;
37 const Kinesis::AABB2D fill { .
min = track.min, .max = { x + handle_w * 0.5F, track.max.y } };
45 el.bounds_hint = handle;
49 return { std::move(fn),
58 glm::vec3 track_color,
59 glm::vec3 handle_color,
60 const std::function<glm::vec2()>& live_offset)
62 GeometryFn<float> fn = [bounds, handle_h, track_color, handle_color, live_offset](
63 float v, std::vector<uint8_t>&
out,
Element& el) {
64 const glm::vec2 shift = live_offset ? live_offset() : glm::vec2(0.F);
67 const float y = live.
min.y + v * (live.
height() - handle_h);
68 const float xl = live.
min.x + live.
width() * 0.35F;
69 const float xr = live.
min.x + live.
width() * 0.65F;
74 const Kinesis::AABB2D fill { .
min = track.min, .max = { track.max.x, y + handle_h * 0.5F } };
82 el.bounds_hint = handle;
86 return { std::move(fn),
97 const std::function<glm::vec2()>& live_offset)
99 const glm::vec2 center = region.
center();
103 float v, std::vector<uint8_t>&
out,
Element& el) {
104 const glm::vec2 shift = live_offset ? live_offset() : glm::vec2(0.F);
105 const glm::vec2 live_center = center + shift;
107 const float angle = angle_start + v * (angle_end - angle_start);
108 const glm::vec2 tip = live_center +
radius * glm::vec2(std::cos(angle), std::sin(angle));
111 const std::vector<V> verts = {
112 { .
position = { live_center.x, live_center.y, 0 }, .color =
color },
113 { .position = { tip.x, tip.y, 0 }, .color =
color },
122 return { std::move(fn),
143 return { std::move(fn),
153 const std::function<glm::vec2()>& live_offset)
156 glm::vec2 v, std::vector<uint8_t>&
out,
Element& el) {
157 const glm::vec2 shift = live_offset ? live_offset() : glm::vec2(0.F);
160 float x = live.
min.x + v.x * live.
width();
161 float y = live.
min.y + v.y * live.
height();
164 std::vector<V> verts = {
170 el.bounds_hint = live;
172 std::array<glm::vec2, 4> {
174 glm::vec2(live.
max.x, live.
min.y),
176 glm::vec2(live.
min.x, live.
max.y) } });
179 return { std::move(fn),
186 std::span<const glm::vec2> path,
188 float half_thickness,
189 glm::vec3 track_color,
190 glm::vec3 fill_color,
191 glm::vec3 handle_color,
193 const std::function<glm::vec2()>& live_offset)
195 std::vector<glm::vec2> pts(path.begin(), path.end());
197 std::vector<float> seg_lengths;
198 seg_lengths.reserve(pts.size() > 0 ? pts.size() - 1 : 0);
199 float total_len = 0.0F;
200 for (
size_t i = 0; i + 1 < pts.size(); ++i) {
201 float l = glm::length(pts[i + 1] - pts[i]);
202 seg_lengths.push_back(l);
207 .max = pts.empty() ? glm::vec2(0.F) : pts[0] };
208 for (
const auto& p : pts) {
209 aabb.
min = glm::min(aabb.
min, p);
210 aabb.
max = glm::max(aabb.
max, p);
212 aabb = aabb.
expanded(half_thickness);
218 seg_lengths = std::move(seg_lengths),
221 handle_buf = std::move(handle_buf),
227 live_offset](
float v, std::vector<uint8_t>&
out,
Element& el) {
228 if (pts.size() < 2) {
233 const glm::vec2 shift = live_offset ? live_offset() : glm::vec2(0.F);
235 const float target = std::clamp(v, 0.0F, 1.0F) * total_len;
240 glm::vec2 handle_pos = pts.front();
241 float accumulated = 0.0F;
242 size_t split_seg = 0;
243 float split_t = 0.0F;
244 for (
size_t i = 0; i < seg_lengths.size(); ++i) {
245 if (accumulated + seg_lengths[i] >= target || i + 1 == seg_lengths.size()) {
246 split_t = seg_lengths[i] > 0.0F
247 ? (target - accumulated) / seg_lengths[i]
249 split_t = std::clamp(split_t, 0.0F, 1.0F);
250 handle_pos = glm::mix(pts[i], pts[i + 1], split_t);
254 accumulated += seg_lengths[i];
258 std::vector<glm::vec2> live_pts;
259 live_pts.reserve(pts.size());
260 for (
const auto& p : pts)
261 live_pts.push_back(p + shift);
266 std::span<const glm::vec2>(live_pts).subspan(0, split_seg + 1),
268 verts.insert(verts.end(), fill.begin(), fill.end());
270 if (split_t > 0.0F) {
271 verts.push_back({ .position = { live_pts[split_seg].x, live_pts[split_seg].y, 0.0F }, .color = fill_color });
272 verts.push_back({ .position = { handle_pos.x, handle_pos.y, 0.0F }, .color = fill_color });
282 .
position = { handle_pos.x, handle_pos.y, 0.0F },
283 .color = handle_color,
286 std::vector<uint8_t> hbytes(
sizeof(hv));
287 std::memcpy(hbytes.data(), &hv,
sizeof(hv));
288 handle_buf.submit(hbytes);
292 return { std::move(fn),
295 drag_with<float>(std::move(project)) };
302 const std::function<glm::vec2()>& live_offset)
305 bool v, std::vector<uint8_t>&
out,
Element& el) {
306 const glm::vec2 shift = live_offset ? live_offset() : glm::vec2(0.F);
310 el.bounds_hint = live;
314 return { std::move(fn),
323 glm::vec3 fill_color,
324 glm::vec3 track_color,
325 const std::function<glm::vec2()>& live_offset)
327 GeometryFn<float> fn = [bounds, horizontal, fill_color, track_color, live_offset](
328 float v, std::vector<uint8_t>&
out,
Element& el) {
329 const glm::vec2 shift = live_offset ? live_offset() : glm::vec2(0.F);
332 const float t = std::clamp(v, 0.F, 1.F);
336 const float split = live.
min.x + t * live.
width();
337 fill = { .min = live.
min, .max = { split, live.
max.y } };
338 remainder = { .min = { split, live.
min.y }, .max = live.
max };
340 const float split = live.
min.y + t * live.
height();
341 fill = { .min = live.
min, .max = { live.
max.x, split } };
342 remainder = { .min = { live.
min.x, split }, .max = live.
max };
347 verts.insert(verts.end(), rest.begin(), rest.end());
351 el.bounds_hint = live;
355 return { std::move(fn),
367 glm::vec2 pos, std::vector<uint8_t>&
out,
Element& el) {
369 const std::array<V, 4> verts { {
370 { .
position = { pos.x - arm_len, pos.y, 0.F }, .color =
color, .thickness = thickness },
371 { .position = { pos.x + arm_len, pos.y, 0.F }, .color =
color, .thickness = thickness },
372 { .position = { pos.x, pos.y - arm_len, 0.F }, .color =
color, .thickness = thickness },
373 { .position = { pos.x, pos.y + arm_len, 0.F }, .color =
color, .thickness = thickness },
380 return { std::move(fn),
390 const std::function<glm::vec2()>& live_offset,
394 const std::vector<float>& v, std::vector<uint8_t>&
out,
Element& el) {
395 const glm::vec2 shift = live_offset ? live_offset() : glm::vec2(0.F);
400 el.bounds_hint = live;
402 std::array<glm::vec2, 4> {
404 glm::vec2(live.
max.x, live.
min.y),
406 glm::vec2(live.
min.x, live.
max.y) } });
410 const auto n = v.size();
411 const float x_step = live.
width() /
static_cast<float>(n - 1);
413 std::vector<Kakshya::LineVertex> verts;
414 verts.reserve((n - 1) * 2);
416 for (
size_t i = 0; i + 1 < n; ++i) {
417 const float xa = live.
min.x +
static_cast<float>(i) * x_step;
418 const float xb = live.
min.x +
static_cast<float>(i + 1) * x_step;
419 const float ya = live.
min.y + std::clamp(v[i], 0.F, 1.F) * live.
height();
420 const float yb = live.
min.y + std::clamp(v[i + 1], 0.F, 1.F) * live.
height();
422 verts.push_back({ .position = { xa, ya, 0.F }, .color =
color, .thickness = thickness });
423 verts.push_back({ .position = { xb, yb, 0.F }, .color =
color, .thickness = thickness });
428 el.bounds_hint = live;
430 std::array<glm::vec2, 4> {
432 glm::vec2(live.
max.x, live.
min.y),
434 glm::vec2(live.
min.x, live.
max.y) } });
448 const std::shared_ptr<
MappedState<std::vector<float>>>& state,
452 std::optional<size_t> prev_index;
454 auto ds = std::make_shared<DragState>();
457 [state, bounds, ds](uint32_t, glm::vec2 ndc) {
458 auto& v = state->value;
462 const float t = (ndc.x - bounds.
min.x) / bounds.
width();
463 const float a = (ndc.y - bounds.
min.y) / bounds.
height();
470 ds->prev_index = std::nullopt;
479 glm::vec2 extent, std::vector<uint8_t>&
out,
Element& el) {
480 const float len = std::clamp(extent.y, 0.F, 1.F) * track.
height();
481 const float top = track.
max.y - std::clamp(extent.x, 0.F, 1.F) * (track.
height() - len);
486 el.bounds_hint = track;
490 return { std::move(fn),
std::vector< float > * out
void on_release(uint32_t id, IO::MouseButtons btn, PressFn fn)
Called when a mouse button is released over an element.
void on_drag(uint32_t id, IO::MouseButtons btn, MoveFn fn)
Called on each mouse-move event while btn is held, tracking the element where the drag began even whe...
Event wiring between a Layer and a window surface.
std::vector< MeshVertex > to_mesh_vertices(std::span< const Vertex > vertices, glm::vec2 weight_range)
Batch-project raw Vertex vector to MeshVertex.
void stroke_samples(std::span< float > samples, std::optional< size_t > &previous, float position, float level) noexcept
Write a level into an evenly spaced sample array at a fractional position, filling linearly from the ...
std::function< bool(glm::vec2)> polygon_bounds(std::span< const glm::vec2 > vertices)
Containment test for a convex or concave polygon.
std::vector< Kakshya::LineVertex > polyline(std::span< const glm::vec2 > pts, glm::vec3 color, float thickness)
Polyline as a LINE_LIST (open path, 2 * (pts.size() - 1) vertices).
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.
std::function< float(glm::vec2)> path_fraction(std::span< const glm::vec2 > points, const std::function< glm::vec2()> &live_offset={})
Normalized arc-length position of the closest point on a polyline.
std::function< float(glm::vec2)> angle_fraction(glm::vec2 center, float angle_start, float angle_end, const std::function< glm::vec2()> &live_offset={}) noexcept
Fraction along an angular sweep about center.
std::function< bool(glm::vec2)> stroke_bounds(std::span< const glm::vec2 > points, float half_thickness)
Containment test for a polyline with a uniform half-thickness.
std::function< bool(glm::vec2)> circular_bounds(glm::vec2 center, float radius) noexcept
Containment test for a circle.
std::array< Kakshya::Vertex, 4 > filled_rect(Kinesis::AABB2D region, glm::vec3 color)
Generate a filled TRIANGLE_STRIP quad from an AABB2D.
std::function< float(glm::vec2)> axis_fraction(AABB2D bounds, float handle_extent=0.F, bool horizontal=true, const std::function< glm::vec2()> &live_offset={}) noexcept
Fraction along one axis of bounds, inverse to fader placement.
Vertex type for line primitives (LINE_LIST / LINE_STRIP topology)
Vertex type for indexed triangle mesh primitives (TRIANGLE_LIST topology)
Vertex type for point primitives (POINT_LIST topology)
static AABB2D from_ndc(glm::vec2 center, glm::vec2 half) noexcept
float height() const noexcept
bool contains(glm::vec2 p) const noexcept
AABB2D translated(glm::vec2 offset) const noexcept
glm::vec2 center() const noexcept
float width() const noexcept
Axis-aligned bounding rectangle in a 2D coordinate space.
bool contains(const glm::vec3 &p) const noexcept
AABB3D translated(const glm::vec3 &offset) const noexcept
AABB3D expanded(float margin) const noexcept