Skip to content

Commit 0522d30

Browse files
committed
add tiling_area
1 parent f7e46d2 commit 0522d30

File tree

2 files changed

+384
-0
lines changed

2 files changed

+384
-0
lines changed
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
/*
2+
ruis - GUI framework
3+
4+
Copyright (C) 2012-2025 Ivan Gagis <[email protected]>
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
/* ================ LICENSE END ================ */
21+
22+
#include "tiling_area.hpp"
23+
24+
#include "../label/rectangle.hpp"
25+
26+
using namespace ruis;
27+
28+
namespace {
29+
const ruis::real minimal_tile_size_pp = 100;
30+
const ruis::real dragger_size_pp = 5;
31+
const uint32_t dragger_color = 0xffff8080;
32+
} // namespace
33+
34+
namespace {
35+
class dragger : public ruis::rectangle // TODO: use gap
36+
{
37+
bool grabbed = false;
38+
ruis::vector2 grab_point;
39+
40+
tiling_area& owner;
41+
42+
ruis::mouse_cursor_stack::iterator arrows_cursor_iter;
43+
44+
public:
45+
std::shared_ptr<ruis::widget> prev_widget;
46+
std::shared_ptr<ruis::widget> next_widget;
47+
48+
dragger(const utki::shared_ref<ruis::context>& c, tiling_area& owner) :
49+
ruis::widget(std::move(c), {}, {}),
50+
ruis::rectangle(this->context, {}, {}),
51+
owner(owner)
52+
{
53+
this->set_color(dragger_color);
54+
}
55+
56+
bool on_mouse_button(const ruis::mouse_button_event& e) override
57+
{
58+
if (e.button != ruis::mouse_button::left) {
59+
return false;
60+
}
61+
62+
this->grabbed = e.is_down;
63+
this->grab_point = e.pos;
64+
65+
if (!this->grabbed) {
66+
if (!this->is_hovered()) {
67+
this->context.get().cursor_stack.pop(this->arrows_cursor_iter);
68+
}
69+
}
70+
71+
return true;
72+
}
73+
74+
bool on_mouse_move(const ruis::mouse_move_event& e) override
75+
{
76+
if (!this->grabbed) {
77+
return false;
78+
}
79+
80+
auto delta = e.pos - this->grab_point;
81+
82+
auto trans_index = this->owner.get_trans_index();
83+
auto long_index = this->owner.get_long_index();
84+
85+
delta[trans_index] = ruis::real(0);
86+
87+
ASSERT(this->prev_widget)
88+
ASSERT(this->next_widget)
89+
auto new_prev_dims = this->prev_widget->rect().d + delta;
90+
auto new_next_dims = this->next_widget->rect().d - delta;
91+
92+
using std::max;
93+
94+
// clamp new tile dimensions to minimum tile size
95+
new_prev_dims = max(new_prev_dims, ruis::vector2(this->owner.min_tile_size));
96+
new_next_dims = max(new_next_dims, ruis::vector2(this->owner.min_tile_size));
97+
98+
if (delta[long_index] >= 0) {
99+
delta = min(delta, this->next_widget->rect().d - new_next_dims);
100+
} else {
101+
delta = max(delta, new_prev_dims - this->prev_widget->rect().d);
102+
}
103+
104+
this->move_by(delta);
105+
106+
this->prev_widget->resize_by(delta);
107+
108+
this->next_widget->resize_by(-delta);
109+
this->next_widget->move_by(delta);
110+
111+
return true;
112+
}
113+
114+
void on_hovered_change(unsigned pointer_id) override
115+
{
116+
if (this->grabbed) {
117+
return;
118+
}
119+
120+
if (this->is_hovered() || grabbed) {
121+
this->arrows_cursor_iter = this->context.get().cursor_stack.push(
122+
this->owner.is_vertical() ? ruis::mouse_cursor::up_down_arrow : ruis::mouse_cursor::left_right_arrow
123+
);
124+
} else {
125+
this->context.get().cursor_stack.pop(this->arrows_cursor_iter);
126+
}
127+
}
128+
129+
void render(const ruis::matrix4& matrix) const override
130+
{
131+
if (this->is_hovered() || this->grabbed) {
132+
this->ruis::rectangle::render(matrix);
133+
}
134+
}
135+
};
136+
} // namespace
137+
138+
tiling_area::tiling_area(
139+
utki::shared_ref<ruis::context> context, //
140+
all_parameters params,
141+
ruis::widget_list children
142+
) :
143+
ruis::widget(std::move(context), {}, {}),
144+
ruis::oriented({.vertical = false}),
145+
ruis::container(this->context, {}, {}),
146+
content_container(ruis::make::container(this->context, {}, std::move(children))),
147+
min_tile_size(this->context.get().units.pp_to_px(minimal_tile_size_pp)),
148+
dragger_size(this->context.get().units.pp_to_px(dragger_size_pp))
149+
{
150+
this->ruis::container::push_back(this->content_container);
151+
this->content_container.get().move_to({0, 0});
152+
}
153+
154+
void tiling_area::on_lay_out()
155+
{
156+
auto long_index = this->get_long_index();
157+
auto trans_index = this->get_trans_index();
158+
159+
using std::max;
160+
161+
// calculate current length of all tiles
162+
ruis::real tiles_length = 0;
163+
164+
for (const auto& t : this->content_container.get()) {
165+
tiles_length +=
166+
max( //
167+
t.get().rect().d[long_index],
168+
this->min_tile_size
169+
);
170+
}
171+
172+
const auto& content_dims = this->rect().d;
173+
174+
using std::round;
175+
176+
// arrange tiles
177+
if (content_dims[long_index] >= tiles_length) {
178+
ruis::vector2 pos{0, 0};
179+
for (auto& t : this->content_container.get()) {
180+
ruis::real tile_length =
181+
max( //
182+
t.get().rect().d[long_index],
183+
this->min_tile_size
184+
);
185+
186+
ASSERT(tiles_length > 0)
187+
188+
ruis::vector2 dims;
189+
dims[trans_index] = content_dims[trans_index];
190+
dims[long_index] = content_dims[long_index] * (tile_length / tiles_length);
191+
dims = round(dims);
192+
t.get().resize(dims);
193+
t.get().move_to(pos);
194+
pos[long_index] += dims[long_index];
195+
}
196+
} else {
197+
ruis::real left_length = content_dims[long_index];
198+
199+
ruis::vector2 pos{0, 0};
200+
201+
for (auto& t : this->content_container.get()) {
202+
ruis::real tile_length = max(t.get().rect().d[long_index], this->min_tile_size);
203+
204+
ASSERT(tiles_length > 0)
205+
206+
ruis::vector2 dims;
207+
dims[trans_index] = content_dims[trans_index];
208+
dims[long_index] = left_length * (tile_length / tiles_length);
209+
if (dims[long_index] <= this->min_tile_size) {
210+
dims[long_index] = this->min_tile_size;
211+
}
212+
tiles_length -= tile_length;
213+
left_length -= dims[long_index];
214+
dims = round(dims);
215+
216+
t.get().resize(dims);
217+
t.get().move_to(pos);
218+
pos[long_index] += dims[long_index];
219+
}
220+
}
221+
222+
this->content_container.get().resize(content_dims);
223+
224+
// ====================
225+
// = lay out draggers =
226+
227+
ASSERT(this->size() >= 1)
228+
229+
auto num_draggers = this->content_container.get().size() == 0 ? 0 : this->content_container.get().size() - 1;
230+
231+
// remove redundant draggers
232+
while (this->size() - 1 > num_draggers) {
233+
this->pop_back();
234+
}
235+
236+
// add missing draggers
237+
while (this->size() - 1 < num_draggers) {
238+
this->push_back(utki::make_shared<dragger>(this->context, *this));
239+
}
240+
241+
ruis::vector2 dragger_dims;
242+
dragger_dims[long_index] = this->dragger_size;
243+
dragger_dims[trans_index] = this->rect().d[trans_index];
244+
245+
for (auto i = std::next(this->begin()); i != this->end(); ++i) {
246+
auto index = size_t(std::distance(this->begin(), i)) - 1;
247+
248+
ASSERT(index < this->content().size())
249+
250+
auto& dragger = dynamic_cast<::dragger&>(i->get());
251+
252+
dragger.prev_widget = this->content().children()[index].to_shared_ptr();
253+
dragger.next_widget = this->content().children()[index + 1].to_shared_ptr();
254+
255+
dragger.resize(dragger_dims);
256+
257+
ruis::vector2 dragger_pos;
258+
dragger_pos[trans_index] = ruis::real(0);
259+
dragger_pos[long_index] = round(dragger.next_widget->rect().p[long_index] - this->dragger_size / 2);
260+
dragger.move_to(dragger_pos);
261+
}
262+
}
263+
264+
ruis::vector2 tiling_area::measure(const ruis::vector2& quotum) const
265+
{
266+
auto long_index = this->get_long_index();
267+
268+
ruis::vector2 ret;
269+
270+
for (size_t i = 0; i != quotum.size(); ++i) {
271+
if (quotum[i] < 0) {
272+
ret[i] = this->min_tile_size;
273+
274+
if (i == long_index) {
275+
ret[i] *= ruis::real(this->content_container.get().size());
276+
}
277+
} else {
278+
ret[i] = quotum[i];
279+
}
280+
}
281+
282+
return ret;
283+
}
284+
285+
utki::shared_ref<ruis::tiling_area> make::tiling_area(
286+
utki::shared_ref<ruis::context> context, //
287+
ruis::tiling_area::all_parameters params,
288+
ruis::widget_list children
289+
)
290+
{
291+
return utki::make_shared<ruis::tiling_area>(
292+
std::move(context), //
293+
std::move(params),
294+
std::move(children)
295+
);
296+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
ruis - GUI framework
3+
4+
Copyright (C) 2012-2025 Ivan Gagis <[email protected]>
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
/* ================ LICENSE END ================ */
21+
22+
#pragma once
23+
24+
#include "../../util/oriented.hpp"
25+
#include "../container.hpp"
26+
27+
namespace ruis {
28+
29+
/*
30+
The tile_area arranges tiles either vertially or horizontally.
31+
The tiles are stored in the content container which is the first container child of the tile_area.
32+
The rest of the children are dragger widgets for dragging tile borders within tile_area with mouse.
33+
*/
34+
class tiling_area :
35+
virtual public widget, //
36+
public ruis::oriented,
37+
private ruis::container
38+
{
39+
utki::shared_ref<ruis::container> content_container;
40+
41+
public:
42+
const ruis::real min_tile_size;
43+
const ruis::real dragger_size;
44+
45+
struct all_parameters {
46+
ruis::layout_parameters layout_params;
47+
ruis::widget::parameters widget_params;
48+
};
49+
50+
tiling_area(
51+
utki::shared_ref<ruis::context> context, //
52+
all_parameters params,
53+
ruis::widget_list children
54+
);
55+
56+
ruis::container& content()
57+
{
58+
return this->content_container.get();
59+
}
60+
61+
const ruis::container& content() const
62+
{
63+
return this->content_container.get();
64+
}
65+
66+
void on_lay_out() override;
67+
68+
ruis::vector2 measure(const ruis::vector2& quotum) const override;
69+
70+
// override in order to avoid invalidation of layout when children list changes,
71+
// because default implementation of this method invalidates layout
72+
void on_children_change() override
73+
{
74+
// do nothing
75+
}
76+
77+
private:
78+
};
79+
80+
namespace make {
81+
utki::shared_ref<ruis::tiling_area> tiling_area(
82+
utki::shared_ref<ruis::context> context, //
83+
ruis::tiling_area::all_parameters params,
84+
ruis::widget_list children
85+
);
86+
} // namespace make
87+
88+
} // namespace ruis

0 commit comments

Comments
 (0)