hikogui封装, 现新增vertical_widget
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "application/application.h"
|
||||
#include "hikogui/hikogui.hpp"
|
||||
#include "widget/mixer/w_mixer_track.h"
|
||||
@@ -31,9 +33,10 @@ public:
|
||||
void init_window() {
|
||||
// get_plugin_host_manager().create_instrument_plugin_host(R"(F:\VST\VST64\Serum_x64.dll)");
|
||||
// 创建一个窗口
|
||||
|
||||
auto widget = std::make_unique<window_widget>(txt("Toggle example"));
|
||||
// 在窗口中放入一个标签控件(A1:B1表示控件占用两格子, 2x1大小, 可能能够通过标签控件的地址来获取控件对象)
|
||||
widget->content().emplace<w_mixer_track>("A1:B1", txt("mixer_track"));
|
||||
widget->content().emplace<w_mixer_track>("A1:B1");
|
||||
|
||||
// 将窗口指针保存到成员变量中
|
||||
window_ = std::make_unique<gui_window>(std::move(widget));
|
||||
|
||||
1
Arona/src/widget/layout/compound_widget.cpp
Normal file
1
Arona/src/widget/layout/compound_widget.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "compound_widget.h"
|
||||
31
Arona/src/widget/layout/compound_widget.h
Normal file
31
Arona/src/widget/layout/compound_widget.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <hikogui/hikogui.hpp>
|
||||
|
||||
#include "slot.h"
|
||||
|
||||
class compound_widget : public hi::widget {
|
||||
public:
|
||||
compound_widget() : hi::widget(nullptr), _slot(this) {
|
||||
}
|
||||
|
||||
compound_widget(hi::widget_intf const* parent) : hi::widget(parent), _slot(this) {
|
||||
}
|
||||
|
||||
hi::box_constraints update_constraints() noexcept override {
|
||||
return _slot.widget->update_constraints();
|
||||
}
|
||||
void set_layout(hi::widget_layout const& context) noexcept override {
|
||||
return _slot.widget->set_layout(context);
|
||||
}
|
||||
void draw(hi::draw_context const& context) noexcept override {
|
||||
return _slot.widget->draw(context);
|
||||
}
|
||||
[[nodiscard]] hi::generator<widget_intf&> children(bool include_invisible) noexcept override {
|
||||
co_yield *_slot.widget;
|
||||
}
|
||||
hi::hitbox hitbox_test(hi::point2 position) const noexcept override {
|
||||
return _slot.widget->hitbox_test(position);
|
||||
}
|
||||
protected:
|
||||
compound_slot _slot;
|
||||
};
|
||||
1
Arona/src/widget/layout/leaf_widget.cpp
Normal file
1
Arona/src/widget/layout/leaf_widget.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "leaf_widget.h"
|
||||
6
Arona/src/widget/layout/leaf_widget.h
Normal file
6
Arona/src/widget/layout/leaf_widget.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include <hikogui/hikogui.hpp>
|
||||
|
||||
class leaf_widget : hi::widget {
|
||||
|
||||
};
|
||||
1
Arona/src/widget/layout/panel_widget.cpp
Normal file
1
Arona/src/widget/layout/panel_widget.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "panel_widget.h"
|
||||
47
Arona/src/widget/layout/panel_widget.h
Normal file
47
Arona/src/widget/layout/panel_widget.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <hikogui/hikogui.hpp>
|
||||
|
||||
class panel_widget : public hi::widget {
|
||||
public:
|
||||
panel_widget() : hi::widget(nullptr) {}
|
||||
panel_widget(hi::widget_intf const* parent) : hi::widget(parent) {}
|
||||
|
||||
virtual hi::widget* get_widget(int32_t index) const noexcept = 0;
|
||||
virtual size_t size() const noexcept = 0;
|
||||
virtual bool empty() const { return size() == 0; }
|
||||
template<typename WidgetType>
|
||||
WidgetType* get_widget(int32_t index) const noexcept {
|
||||
return dynamic_cast<WidgetType*>(get_widget(index));
|
||||
}
|
||||
|
||||
hi::widget* operator[](int32_t index) const noexcept {
|
||||
return get_widget(index);
|
||||
}
|
||||
|
||||
explicit operator std::shared_ptr<hi::widget>() {
|
||||
return std::shared_ptr<hi::widget>(this);
|
||||
}
|
||||
};
|
||||
|
||||
#define IMPL_PANEL_SLOT(WIDGET_TYPE, SLOT_TYPE) \
|
||||
typedef SLOT_TYPE slot_type; \
|
||||
template<typename WidgetType> \
|
||||
slot_type& add() { \
|
||||
auto child_widget = std::make_shared<WidgetType>(this); \
|
||||
slot_type new_slot(this); \
|
||||
new_slot.widget = child_widget; \
|
||||
return push_slot(new_slot); \
|
||||
} \
|
||||
template<typename WidgetType, typename ...Args> \
|
||||
slot_type& add(Args ...args) { \
|
||||
auto child_widget = std::make_shared<WidgetType>(this, args...); \
|
||||
slot_type new_slot(this); \
|
||||
new_slot.widget = child_widget; \
|
||||
return push_slot(new_slot); \
|
||||
} \
|
||||
slot_type& add(std::shared_ptr<hi::widget> child_widget) { \
|
||||
slot_type new_slot(this); \
|
||||
new_slot.widget = child_widget; \
|
||||
return push_slot(new_slot); \
|
||||
} \
|
||||
slot_type& push_slot(const slot_type& in_slot)
|
||||
104
Arona/src/widget/layout/slot.h
Normal file
104
Arona/src/widget/layout/slot.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
#include <hikogui/hikogui.hpp>
|
||||
|
||||
#define BEGIN_SLOT_PARAMS(SLOT_TYPE) typedef SLOT_TYPE slot_type;
|
||||
|
||||
#define SLOT_PARAM(Type, Name) \
|
||||
public:\
|
||||
slot_type& Name(Type in_##Name) { \
|
||||
_##Name = in_##Name; \
|
||||
return *this; \
|
||||
} \
|
||||
const Type& get_##Name() const { \
|
||||
return _##Name; \
|
||||
} \
|
||||
protected: \
|
||||
Type _##Name;
|
||||
|
||||
struct size_param {
|
||||
enum rule {
|
||||
auto_size,
|
||||
stretch,
|
||||
};
|
||||
rule size_rule;
|
||||
hi::observer<float> value;
|
||||
protected:
|
||||
size_param(rule in_rule, const hi::observer<float>& in_value) : size_rule(in_rule), value(in_value) {}
|
||||
};
|
||||
|
||||
struct stretch_param : size_param {
|
||||
stretch_param(const hi::observer<float>& in_value) : size_param(size_param::stretch, in_value) {}
|
||||
stretch_param() : size_param(size_param::stretch, 1.f) {}
|
||||
};
|
||||
|
||||
struct auto_size_param : size_param {
|
||||
auto_size_param() : size_param(size_param::auto_size, 0.f) {}
|
||||
};
|
||||
|
||||
template<typename SlotType>
|
||||
class basic_slot {
|
||||
public:
|
||||
hi::widget* slot_owner;
|
||||
std::shared_ptr<hi::widget> widget;
|
||||
|
||||
basic_slot(hi::widget* in_slot_owner) : slot_owner(in_slot_owner) {
|
||||
|
||||
}
|
||||
|
||||
template<typename WidgetType>
|
||||
SlotType& make_content() {
|
||||
widget = std::make_shared<WidgetType>(slot_owner);
|
||||
return *static_cast<SlotType*>(this);
|
||||
}
|
||||
|
||||
template<typename WidgetType, typename ...Args>
|
||||
SlotType& make_content(Args ...args) {
|
||||
widget = std::make_shared<WidgetType>(slot_owner, args...);
|
||||
return *static_cast<SlotType*>(this);
|
||||
}
|
||||
|
||||
SlotType& set_content(std::shared_ptr<hi::widget> in_widget) {
|
||||
widget = in_widget;
|
||||
widget->parent = slot_owner;
|
||||
return *static_cast<SlotType*>(this);
|
||||
}
|
||||
|
||||
SlotType& operator[](std::shared_ptr<hi::widget> in_widget) {
|
||||
widget = in_widget;
|
||||
widget->parent = slot_owner;
|
||||
return *static_cast<SlotType*>(this);
|
||||
}
|
||||
};
|
||||
|
||||
class compound_slot : public basic_slot<compound_slot> {
|
||||
public:
|
||||
compound_slot(hi::widget* in_owner) : basic_slot<compound_slot>(in_owner) {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename SlotType>
|
||||
class panel_slot : public basic_slot<SlotType> {
|
||||
public:
|
||||
size_param size;
|
||||
hi::box_shape shape;
|
||||
hi::margins margin;
|
||||
|
||||
explicit panel_slot() : basic_slot<SlotType>(nullptr), size(auto_size_param()) {
|
||||
}
|
||||
|
||||
panel_slot(hi::widget* in_owner) : basic_slot<SlotType>(in_owner), size(auto_size_param()) {
|
||||
}
|
||||
|
||||
SlotType& auto_size() {
|
||||
size = auto_size_param();
|
||||
return *this;
|
||||
}
|
||||
SlotType& stretch() {
|
||||
size = stretch_param();
|
||||
return *this;
|
||||
}
|
||||
SlotType& stretch(const hi::observer<float>& in_value) {
|
||||
size = stretch_param(in_value);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
1
Arona/src/widget/layout/vertical_widget.cpp
Normal file
1
Arona/src/widget/layout/vertical_widget.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "vertical_widget.h"
|
||||
85
Arona/src/widget/layout/vertical_widget.h
Normal file
85
Arona/src/widget/layout/vertical_widget.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
#include "panel_widget.h"
|
||||
#include "slot.h"
|
||||
#include <hikogui/hikogui.hpp>
|
||||
|
||||
|
||||
|
||||
class vertical_widget : public panel_widget {
|
||||
public:
|
||||
class slot : public panel_slot<slot> {
|
||||
public:
|
||||
slot() : panel_slot(), _horizontal_alignment(hi::horizontal_alignment::left), _vertical_alignment(hi::vertical_alignment::top) {}
|
||||
explicit slot(vertical_widget* owner) : panel_slot(owner), _horizontal_alignment(hi::horizontal_alignment::left), _vertical_alignment(hi::vertical_alignment::top) {
|
||||
}
|
||||
|
||||
BEGIN_SLOT_PARAMS(slot)
|
||||
SLOT_PARAM(hi::horizontal_alignment, horizontal_alignment)
|
||||
SLOT_PARAM(hi::vertical_alignment, vertical_alignment)
|
||||
};
|
||||
vertical_widget() = default;
|
||||
explicit vertical_widget(hi::widget* parent) : panel_widget(parent) {}
|
||||
|
||||
IMPL_PANEL_SLOT(vertical_widget, slot) {
|
||||
return _row_layout.push_back(in_slot).value;
|
||||
}
|
||||
|
||||
void draw(hi::draw_context const& context) noexcept override {
|
||||
if (mode() <= hi::widget_mode::invisible) {
|
||||
return;
|
||||
}
|
||||
for (auto& slot : _row_layout) {
|
||||
slot.value.widget->draw(context);
|
||||
}
|
||||
}
|
||||
|
||||
hi::hitbox hitbox_test(hi::point2 position) const noexcept override {
|
||||
hi_axiom(hi::loop::main().on_thread());
|
||||
if (mode() >= hi::widget_mode::partial) {
|
||||
auto r = hi::hitbox{};
|
||||
for (auto& slot : _row_layout) {
|
||||
r = slot.value.widget->hitbox_test_from_parent(position, r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
return hi::hitbox{};
|
||||
}
|
||||
|
||||
void set_layout(hi::widget_layout const& context) noexcept override {
|
||||
if (hi::compare_store(_layout, context)) {
|
||||
_row_layout.set_layout(context.shape, theme().baseline_adjustment());
|
||||
}
|
||||
|
||||
for (const auto& cell: _row_layout) {
|
||||
cell.value.widget->set_layout(context.transform(cell.shape, hi::transform_command::level));
|
||||
}
|
||||
}
|
||||
|
||||
hi::box_constraints update_constraints() noexcept override {
|
||||
_layout = {};
|
||||
for (auto& cell: _row_layout) {
|
||||
cell.set_constraints(cell.value.widget->update_constraints());
|
||||
}
|
||||
auto grid_constraints = _row_layout.constraints(hi::os_settings::left_to_right());
|
||||
return grid_constraints.constrain(*minimum, *maximum);
|
||||
}
|
||||
|
||||
[[nodiscard]] hi::generator<widget_intf&> children(bool include_invisible) noexcept override {
|
||||
for (auto& slot : _row_layout) {
|
||||
co_yield *slot.value.widget;
|
||||
}
|
||||
}
|
||||
|
||||
hi::widget* get_widget(int32_t index) const noexcept override {
|
||||
return _row_layout[index].value.widget.get();
|
||||
}
|
||||
size_t size() const noexcept override {
|
||||
return _row_layout.size();
|
||||
}
|
||||
|
||||
hi::observer<float> child_height_adjustment = 0.f;
|
||||
private:
|
||||
hi::column_layout<slot> _row_layout;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,208 +11,27 @@ class slider : public hi::widget {
|
||||
public:
|
||||
using super = hi::widget;
|
||||
|
||||
slider(hi::widget_intf const* in_parent, bool horizontal, slider_mode in_mode) noexcept;
|
||||
|
||||
slider_mode _mode;
|
||||
hi::observer<T> min_value;
|
||||
hi::observer<T> max_value;
|
||||
hi::observer<T> value;
|
||||
bool _horizontal = true;
|
||||
|
||||
const float bar_width = 20; // 当水平时,值为bar的宽度
|
||||
slider_mode _mode;
|
||||
std::string value_to_string()
|
||||
{
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
std::ostringstream ss;
|
||||
ss << std::fixed << std::setprecision(2) << *value;
|
||||
return ss.str();
|
||||
} else if constexpr (std::is_same_v<T, int>) {
|
||||
return std::to_string(*value);
|
||||
} else {
|
||||
return "Unknown type";
|
||||
}
|
||||
}
|
||||
|
||||
void delta_value(float delta) {
|
||||
_internal_value += delta;
|
||||
_internal_value = std::clamp<T>(static_cast<T>(_internal_value), *min_value, *max_value);
|
||||
value = _internal_value;
|
||||
}
|
||||
|
||||
void set_value(T in_value) {
|
||||
in_value = std::clamp<T>(in_value, *min_value, *max_value);
|
||||
_internal_value = in_value;
|
||||
value = _internal_value;
|
||||
}
|
||||
|
||||
slider(hi::widget const* parent, bool horizontal, slider_mode in_mode) noexcept : hi::widget(parent), _horizontal(horizontal) {
|
||||
min_value = static_cast<T>(0);
|
||||
max_value = static_cast<T>(1);
|
||||
value = static_cast<T>(0.5);
|
||||
_internal_value = *value;
|
||||
_mode = in_mode;
|
||||
_value_string = value_to_string();
|
||||
_value_text = std::make_unique<hi::text_widget>(this, _value_string, hi::alignment::middle_center());
|
||||
|
||||
_value_changed = value.subscribe([this](T in_value) {
|
||||
_internal_value = in_value;
|
||||
_value_string = value_to_string();
|
||||
request_redraw();
|
||||
});
|
||||
_max_value_changed = max_value.subscribe([this](T in_value) {
|
||||
if (_internal_value > in_value) {
|
||||
_internal_value = in_value;
|
||||
value = _internal_value;
|
||||
}
|
||||
request_redraw();
|
||||
});
|
||||
_min_value_changed = min_value.subscribe([this](T in_value) {
|
||||
if (_internal_value < in_value) {
|
||||
_internal_value = in_value;
|
||||
value = _internal_value;
|
||||
}
|
||||
request_redraw();
|
||||
});
|
||||
}
|
||||
|
||||
hi::box_constraints update_constraints() noexcept override {
|
||||
auto _constraints = hi::widget::update_constraints();
|
||||
if (_horizontal) {
|
||||
_constraints.minimum = {1.0f, bar_width};
|
||||
_constraints.preferred = {1.0f, bar_width};
|
||||
_constraints.maximum = {hi::large_number_v<float>, bar_width};
|
||||
_constraints.alignment = hi::alignment::middle_center();
|
||||
} else {
|
||||
_constraints.minimum = {bar_width, 1.0f};
|
||||
_constraints.preferred = {bar_width, 1.0f};
|
||||
_constraints.maximum = {bar_width, hi::large_number_v<float>};
|
||||
_constraints.alignment = hi::alignment::middle_center();
|
||||
}
|
||||
auto text_constraints = _value_text->update_constraints();
|
||||
_constraints = max(_constraints, text_constraints);
|
||||
_constraints.margins = 0;
|
||||
return _constraints;
|
||||
}
|
||||
|
||||
hi::hitbox hitbox_test(hi::point2 position) const noexcept override {
|
||||
hi_axiom(hi::loop::main().on_thread());
|
||||
|
||||
if (mode() >= hi::widget_mode::partial and layout().contains(position)) {
|
||||
return {id, _layout.elevation, hi::hitbox_type::scroll_bar};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool accepts_keyboard_focus(hi::keyboard_focus_group group) const noexcept override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle_event(hi::gui_event const& event) noexcept override {
|
||||
hi_axiom(hi::loop::main().on_thread());
|
||||
if (mode() < hi::widget_mode::partial)
|
||||
return super::handle_event(event);
|
||||
switch (event.type()) {
|
||||
case hi::gui_event_type::keyboard_down:
|
||||
if (hi::to_bool(event.keyboard_modifiers & hi::keyboard_modifiers::control)) {
|
||||
_micro_control = true;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case hi::gui_event_type::mouse_down:
|
||||
if (event.mouse().cause.left_button) {
|
||||
_down_value = _internal_value;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case hi::gui_event_type::mouse_drag:
|
||||
if (event.mouse().cause.left_button) {
|
||||
if (_mode == slider_mode::mouse_snap) {
|
||||
auto position = event.mouse().position;
|
||||
if (_micro_control) {
|
||||
position.x() *= 0.1f;
|
||||
position.y() *= 0.1f;
|
||||
}
|
||||
if (_horizontal) {
|
||||
float const current_width = _layout.rectangle().width();
|
||||
float const new_value_width = std::clamp(position.x(), 0.0f, current_width);
|
||||
set_value(new_value_width / current_width);
|
||||
} else {
|
||||
float const current_height = _layout.rectangle().height();
|
||||
float const new_value_height = std::clamp(position.y(), 0.0f, current_height);
|
||||
set_value(new_value_height / current_height);
|
||||
}
|
||||
} else {
|
||||
if (_horizontal) {
|
||||
const float delta = event.drag_delta().x() / _layout.rectangle().width();
|
||||
set_value(delta + _down_value);
|
||||
} else {
|
||||
const float delta = event.drag_delta().y() / _layout.rectangle().height();
|
||||
set_value(delta + _down_value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case hi::gui_event_type::mouse_wheel:
|
||||
{
|
||||
delta_value(event.mouse().wheel_delta.y() * (_micro_control ? 0.0001f : 0.001f));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case hi::gui_event_type::mouse_enter:
|
||||
_is_mouse_hoverd = true;
|
||||
break;
|
||||
case hi::gui_event_type::mouse_exit:
|
||||
case hi::gui_event_type::mouse_exit_window:
|
||||
case hi::gui_event_type::keyboard_exit:
|
||||
_micro_control = false;
|
||||
_is_mouse_hoverd = false;
|
||||
default:;
|
||||
}
|
||||
|
||||
return super::handle_event(event);
|
||||
}
|
||||
|
||||
void draw(hi::draw_context const& context) noexcept override {
|
||||
if (mode() <= hi::widget_mode::invisible || !overlaps(context, layout())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto const current_value = *value;
|
||||
if (current_value > 0.0f) {
|
||||
auto const bar_color = theme().color(hi::semantic_color::blue, _layout.layer - 1);
|
||||
if (_horizontal) {
|
||||
auto bar_rectangle = _layout.rectangle();
|
||||
|
||||
float const value_width = bar_rectangle.width() * current_value;
|
||||
bar_rectangle.set_width(value_width);
|
||||
|
||||
context.draw_box(
|
||||
_layout,
|
||||
bar_rectangle,
|
||||
bar_color);
|
||||
} else {
|
||||
auto bar_rectangle = _layout.rectangle();
|
||||
auto const bar_height = bar_rectangle.height() * current_value;
|
||||
bar_rectangle.set_height(bar_height);
|
||||
context.draw_box(
|
||||
_layout,
|
||||
bar_rectangle,
|
||||
bar_color);
|
||||
}
|
||||
}
|
||||
// Draw value text
|
||||
if (_is_mouse_hoverd)
|
||||
_value_text->draw(context);
|
||||
}
|
||||
|
||||
void set_layout(hi::widget_layout const &context) noexcept override {
|
||||
if (hi::compare_store(_layout, context)) {
|
||||
_value_text->set_layout(context.transform(context.shape, hi::transform_command::level));
|
||||
}
|
||||
}
|
||||
std::string value_to_string();
|
||||
void delta_value(float delta);
|
||||
void set_current_value(T in_value);
|
||||
hi::box_constraints update_constraints() noexcept override;
|
||||
hi::hitbox hitbox_test(hi::point2 position) const noexcept override;
|
||||
bool accepts_keyboard_focus(hi::keyboard_focus_group group) const noexcept override;
|
||||
bool handle_event(hi::gui_event const& event) noexcept override;
|
||||
void draw(hi::draw_context const& context) noexcept override;
|
||||
void set_layout(hi::widget_layout const &context) noexcept override;
|
||||
protected:
|
||||
[[nodiscard]] hi::generator<widget_intf&> children(bool include_invisible) noexcept override {
|
||||
co_yield *_value_text;
|
||||
}
|
||||
[[nodiscard]] hi::generator<widget_intf&> children(bool include_invisible) noexcept override;
|
||||
|
||||
private:
|
||||
float _internal_value = 0;
|
||||
float _down_value = 0;
|
||||
@@ -225,3 +44,209 @@ private:
|
||||
hi::observer<hi::txt> _value_string;
|
||||
bool _is_mouse_hoverd = false;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
slider<T>::slider(hi::widget_intf const* in_parent, bool horizontal, slider_mode in_mode) noexcept: hi::widget(in_parent), _horizontal(horizontal) {
|
||||
min_value = static_cast<T>(0);
|
||||
max_value = static_cast<T>(1);
|
||||
value = static_cast<T>(0.5);
|
||||
_internal_value = *value;
|
||||
_mode = in_mode;
|
||||
_value_string = value_to_string();
|
||||
_value_text = std::make_unique<hi::text_widget>(this, _value_string, hi::alignment::middle_center());
|
||||
|
||||
_value_changed = value.subscribe([this](T in_value) {
|
||||
_internal_value = in_value;
|
||||
_value_string = value_to_string();
|
||||
request_redraw();
|
||||
});
|
||||
_max_value_changed = max_value.subscribe([this](T in_value) {
|
||||
if (_internal_value > in_value) {
|
||||
_internal_value = in_value;
|
||||
value = _internal_value;
|
||||
}
|
||||
request_redraw();
|
||||
});
|
||||
_min_value_changed = min_value.subscribe([this](T in_value) {
|
||||
if (_internal_value < in_value) {
|
||||
_internal_value = in_value;
|
||||
value = _internal_value;
|
||||
}
|
||||
request_redraw();
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string slider<T>::value_to_string() {
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
std::ostringstream ss;
|
||||
ss << std::fixed << std::setprecision(2) << *value;
|
||||
return ss.str();
|
||||
} else if constexpr (std::is_same_v<T, int>) {
|
||||
return std::to_string(*value);
|
||||
} else {
|
||||
return "Unknown type";
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void slider<T>::delta_value(float delta) {
|
||||
_internal_value += delta;
|
||||
_internal_value = std::clamp<T>(static_cast<T>(_internal_value), *min_value, *max_value);
|
||||
value = _internal_value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void slider<T>::set_current_value(T in_value) {
|
||||
in_value = std::clamp<T>(in_value, *min_value, *max_value);
|
||||
_internal_value = in_value;
|
||||
value = _internal_value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
hi::box_constraints slider<T>::update_constraints() noexcept {
|
||||
auto _constraints = hi::widget::update_constraints();
|
||||
if (_horizontal) {
|
||||
_constraints.minimum = {1.0f, bar_width};
|
||||
_constraints.preferred = {1.0f, bar_width};
|
||||
_constraints.maximum = {hi::large_number_v<float>, bar_width};
|
||||
_constraints.alignment = hi::alignment::middle_center();
|
||||
} else {
|
||||
_constraints.minimum = {bar_width, 1.0f};
|
||||
_constraints.preferred = {bar_width, 1.0f};
|
||||
_constraints.maximum = {bar_width, hi::large_number_v<float>};
|
||||
_constraints.alignment = hi::alignment::middle_center();
|
||||
}
|
||||
auto text_constraints = _value_text->update_constraints();
|
||||
_constraints = max(_constraints, text_constraints);
|
||||
_constraints.margins = 0;
|
||||
return _constraints;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
hi::hitbox slider<T>::hitbox_test(hi::point2 position) const noexcept {
|
||||
hi_axiom(hi::loop::main().on_thread());
|
||||
|
||||
if (mode() >= hi::widget_mode::partial and layout().contains(position)) {
|
||||
return {id, _layout.elevation, hi::hitbox_type::scroll_bar};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool slider<T>::accepts_keyboard_focus(hi::keyboard_focus_group group) const noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool slider<T>::handle_event(hi::gui_event const& event) noexcept {
|
||||
hi_axiom(hi::loop::main().on_thread());
|
||||
if (mode() < hi::widget_mode::partial)
|
||||
return super::handle_event(event);
|
||||
switch (event.type()) {
|
||||
case hi::gui_event_type::keyboard_down:
|
||||
if (hi::to_bool(event.keyboard_modifiers & hi::keyboard_modifiers::control)) {
|
||||
_micro_control = true;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case hi::gui_event_type::mouse_down:
|
||||
if (event.mouse().cause.left_button) {
|
||||
_down_value = _internal_value;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case hi::gui_event_type::mouse_drag:
|
||||
if (event.mouse().cause.left_button) {
|
||||
if (_mode == slider_mode::mouse_snap) {
|
||||
auto position = event.mouse().position;
|
||||
if (_micro_control) {
|
||||
position.x() *= 0.1f;
|
||||
position.y() *= 0.1f;
|
||||
}
|
||||
if (_horizontal) {
|
||||
float const current_width = _layout.rectangle().width();
|
||||
float const new_value_width = std::clamp(position.x(), 0.0f, current_width);
|
||||
set_current_value(new_value_width / current_width);
|
||||
} else {
|
||||
float const current_height = _layout.rectangle().height();
|
||||
float const new_value_height = std::clamp(position.y(), 0.0f, current_height);
|
||||
set_current_value(new_value_height / current_height);
|
||||
}
|
||||
} else {
|
||||
if (_horizontal) {
|
||||
const float delta = event.drag_delta().x() / _layout.rectangle().width();
|
||||
set_current_value(delta + _down_value);
|
||||
} else {
|
||||
const float delta = event.drag_delta().y() / _layout.rectangle().height();
|
||||
set_current_value(delta + _down_value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case hi::gui_event_type::mouse_wheel:
|
||||
{
|
||||
delta_value(event.mouse().wheel_delta.y() * (_micro_control ? 0.0001f : 0.001f));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case hi::gui_event_type::mouse_enter:
|
||||
_is_mouse_hoverd = true;
|
||||
break;
|
||||
case hi::gui_event_type::mouse_exit:
|
||||
case hi::gui_event_type::mouse_exit_window:
|
||||
case hi::gui_event_type::keyboard_exit:
|
||||
_micro_control = false;
|
||||
_is_mouse_hoverd = false;
|
||||
default:;
|
||||
}
|
||||
|
||||
return super::handle_event(event);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void slider<T>::draw(hi::draw_context const& context) noexcept {
|
||||
if (mode() <= hi::widget_mode::invisible || !overlaps(context, layout())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto const current_value = *value;
|
||||
if (current_value > 0.0f) {
|
||||
auto const bar_color = theme().color(hi::semantic_color::blue, _layout.layer - 1);
|
||||
if (_horizontal) {
|
||||
auto bar_rectangle = _layout.rectangle();
|
||||
|
||||
float const value_width = bar_rectangle.width() * current_value;
|
||||
bar_rectangle.set_width(value_width);
|
||||
|
||||
context.draw_box(
|
||||
_layout,
|
||||
bar_rectangle,
|
||||
bar_color);
|
||||
} else {
|
||||
auto bar_rectangle = _layout.rectangle();
|
||||
auto const bar_height = bar_rectangle.height() * current_value;
|
||||
bar_rectangle.set_height(bar_height);
|
||||
context.draw_box(
|
||||
_layout,
|
||||
bar_rectangle,
|
||||
bar_color);
|
||||
}
|
||||
}
|
||||
// Draw value text
|
||||
if (_is_mouse_hoverd)
|
||||
_value_text->draw(context);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void slider<T>::set_layout(hi::widget_layout const& context) noexcept {
|
||||
if (hi::compare_store(_layout, context)) {
|
||||
_value_text->set_layout(context.transform(context.shape, hi::transform_command::level));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
hi::generator<hi::widget_intf&> slider<T>::children(bool include_invisible) noexcept {
|
||||
co_yield *_value_text;
|
||||
}
|
||||
|
||||
@@ -1 +1,67 @@
|
||||
#include "volume_bar.h"
|
||||
|
||||
volume_bar::volume_bar(const bool horizontal) noexcept: hi::widget(nullptr), _horizontal(horizontal) {
|
||||
|
||||
}
|
||||
|
||||
hi::box_constraints volume_bar::update_constraints() noexcept {
|
||||
_constraints = hi::widget::update_constraints();
|
||||
if (_horizontal) {
|
||||
_constraints.minimum = {1.0f, bar_width};
|
||||
_constraints.preferred = {1.0f, bar_width};
|
||||
_constraints.maximum = {large_number_v<float>, bar_width};
|
||||
_constraints.alignment = hi::alignment::top_left();
|
||||
} else {
|
||||
_constraints.minimum = {bar_width, 1.0f};
|
||||
_constraints.preferred = {bar_width, 1.0f};
|
||||
_constraints.maximum = {bar_width, large_number_v<float>};
|
||||
_constraints.alignment = hi::vertical_alignment::top;
|
||||
}
|
||||
// On left side a check mark, on right side short-cut. Around the label extra margin.
|
||||
// auto const extra_size = extent2{theme().margin<float>() * 2.0f, theme().margin<float>() * 2.0f};
|
||||
//
|
||||
// auto constraints = _constraints + extra_size;
|
||||
// constraints.margins = 0;
|
||||
_constraints.margins = 0;
|
||||
return _constraints;
|
||||
}
|
||||
|
||||
void volume_bar::draw(hi::draw_context const& context) noexcept {
|
||||
if (mode() <= hi::widget_mode::invisible || !overlaps(context, layout())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw the volume bar itself.
|
||||
auto const current_value = *value;
|
||||
if (current_value > 0.0f) {
|
||||
auto const bar_color = theme().color(semantic_color::blue, _layout.layer - 1);
|
||||
if (_horizontal) {
|
||||
auto bar_rectangle = _layout.rectangle();
|
||||
|
||||
float const value_width = bar_rectangle.width() * current_value;
|
||||
bar_rectangle.set_width(value_width);
|
||||
|
||||
context.draw_box(
|
||||
_layout,
|
||||
bar_rectangle,
|
||||
bar_color);
|
||||
} else {
|
||||
auto bar_rectangle = _layout.rectangle();
|
||||
auto const bar_height = bar_rectangle.height() * current_value;
|
||||
bar_rectangle.set_height(bar_height);
|
||||
context.draw_box(
|
||||
_layout,
|
||||
bar_rectangle,
|
||||
bar_color);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the background of the volume bar.
|
||||
context.draw_box(
|
||||
_layout,
|
||||
_layout.rectangle(),
|
||||
hi::color::transparent(),
|
||||
hi::color::white(),
|
||||
theme().border_width(),
|
||||
hi::border_side::on);
|
||||
}
|
||||
|
||||
@@ -6,73 +6,17 @@ using namespace hi;
|
||||
class volume_bar : public hi::widget {
|
||||
public:
|
||||
// Construct a volume bar widget.
|
||||
volume_bar(hi::widget const *parent, const bool horizontal) noexcept : hi::widget(parent), _horizontal(horizontal) {
|
||||
|
||||
volume_bar(widget_intf const* parent, const bool horizontal) : widget(parent), _horizontal(horizontal) {
|
||||
}
|
||||
volume_bar(const bool horizontal) noexcept;
|
||||
|
||||
hi::observer<float> value = 0.3f;
|
||||
float bar_width = 10; // 当水平时,值为bar的宽度
|
||||
|
||||
hi::box_constraints update_constraints() noexcept override {
|
||||
_constraints = hi::widget::update_constraints();
|
||||
if (_horizontal) {
|
||||
_constraints.minimum = {1.0f, bar_width};
|
||||
_constraints.preferred = {1.0f, bar_width};
|
||||
_constraints.maximum = {large_number_v<float>, bar_width};
|
||||
_constraints.alignment = hi::alignment::top_left();
|
||||
} else {
|
||||
_constraints.minimum = {bar_width, 1.0f};
|
||||
_constraints.preferred = {bar_width, 1.0f};
|
||||
_constraints.maximum = {bar_width, large_number_v<float>};
|
||||
_constraints.alignment = hi::vertical_alignment::top;
|
||||
}
|
||||
// On left side a check mark, on right side short-cut. Around the label extra margin.
|
||||
// auto const extra_size = extent2{theme().margin<float>() * 2.0f, theme().margin<float>() * 2.0f};
|
||||
//
|
||||
// auto constraints = _constraints + extra_size;
|
||||
// constraints.margins = 0;
|
||||
_constraints.margins = 0;
|
||||
return _constraints;
|
||||
}
|
||||
hi::box_constraints update_constraints() noexcept override;
|
||||
|
||||
void draw(hi::draw_context const& context) noexcept override {
|
||||
if (mode() <= hi::widget_mode::invisible || !overlaps(context, layout())) {
|
||||
return;
|
||||
}
|
||||
void draw(hi::draw_context const& context) noexcept override;
|
||||
|
||||
// Draw the volume bar itself.
|
||||
auto const current_value = *value;
|
||||
if (current_value > 0.0f) {
|
||||
auto const bar_color = theme().color(semantic_color::blue, _layout.layer - 1);
|
||||
if (_horizontal) {
|
||||
auto bar_rectangle = _layout.rectangle();
|
||||
|
||||
float const value_width = bar_rectangle.width() * current_value;
|
||||
bar_rectangle.set_width(value_width);
|
||||
|
||||
context.draw_box(
|
||||
_layout,
|
||||
bar_rectangle,
|
||||
bar_color);
|
||||
} else {
|
||||
auto bar_rectangle = _layout.rectangle();
|
||||
auto const bar_height = bar_rectangle.height() * current_value;
|
||||
bar_rectangle.set_height(bar_height);
|
||||
context.draw_box(
|
||||
_layout,
|
||||
bar_rectangle,
|
||||
bar_color);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the background of the volume bar.
|
||||
context.draw_box(
|
||||
_layout,
|
||||
_layout.rectangle(),
|
||||
hi::color::transparent(),
|
||||
hi::color::white(),
|
||||
theme().border_width(),
|
||||
hi::border_side::on);
|
||||
}
|
||||
private:
|
||||
const bool _horizontal = false;
|
||||
hi::box_constraints _constraints;
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include "w_mixer_track.h"
|
||||
|
||||
|
||||
@@ -1,103 +1,27 @@
|
||||
#pragma once
|
||||
#include "hikogui/hikogui.hpp"
|
||||
#include "widget/layout/compound_widget.h"
|
||||
#include "widget/layout/vertical_widget.h"
|
||||
#include "widget/misc/slider.h"
|
||||
#include "widget/misc/volume_bar.h"
|
||||
|
||||
class w_mixer_track : public hi::widget {
|
||||
class w_mixer_track : public compound_widget {
|
||||
public:
|
||||
// Every constructor of a widget starts with a `window` and `parent` argument.
|
||||
// In most cases these are automatically filled in when calling a container widget's `emplace()` function.
|
||||
template<typename Label>
|
||||
w_mixer_track(hi::not_null<widget_intf const *> parent, Label&& in_label) noexcept : widget(parent)
|
||||
w_mixer_track(widget_intf const* parent) : compound_widget(parent)
|
||||
{
|
||||
// Our child widget is a `label_widget` which requires a label to be passed as an third argument.
|
||||
// We use a templated argument to forward the label into the `label_widget`.
|
||||
auto text_widget = std::make_unique<hi::text_widget>(this, std::forward<Label>(in_label), hi::alignment::top_flush());
|
||||
auto l_volume_bar = std::make_unique<volume_bar>(this, true);
|
||||
auto r_volume_bar = std::make_unique<volume_bar>(this, true);
|
||||
auto volume_control = std::make_unique<slider<float>>(this, true, slider_mode::mouse_drag);
|
||||
_label = txt("mixer_track");
|
||||
|
||||
_text_widget = text_widget.get();
|
||||
_l_volume_bar = l_volume_bar.get();
|
||||
_r_volume_bar = r_volume_bar.get();
|
||||
_volume_control = volume_control.get();
|
||||
const auto vertical = std::make_shared<vertical_widget>();
|
||||
|
||||
_row.push_back(std::move(text_widget));
|
||||
_row.push_back(std::move(l_volume_bar));
|
||||
_row.push_back(std::move(r_volume_bar));
|
||||
_row.push_back(std::move(volume_control));
|
||||
}
|
||||
vertical->add<text_widget>(_label.sub<"text">());
|
||||
vertical->add<volume_bar>(true).horizontal_alignment(horizontal_alignment::center);
|
||||
vertical->add<volume_bar>(true);
|
||||
vertical->add<slider<float>>(true, slider_mode::mouse_drag);
|
||||
|
||||
hitbox hitbox_test(point2 position) const noexcept override {
|
||||
hi_axiom(loop::main().on_thread());
|
||||
|
||||
if (mode() >= widget_mode::partial) {
|
||||
auto r = hitbox{};
|
||||
for (auto const& cell : _row) {
|
||||
r = cell.value->hitbox_test_from_parent(position, r);
|
||||
}
|
||||
return r;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// 计算控件所需空间
|
||||
hi::box_constraints update_constraints() noexcept override {
|
||||
_layout = {};
|
||||
|
||||
for (auto& cell : _row) {
|
||||
cell.set_constraints(cell.value->update_constraints());
|
||||
}
|
||||
auto grid_constraints = _row.constraints(os_settings::left_to_right());
|
||||
return grid_constraints.constrain(*minimum, *maximum);
|
||||
}
|
||||
|
||||
void set_layout(hi::widget_layout const& context) noexcept override {
|
||||
if (compare_store(_layout, context)) {
|
||||
_row.set_layout(context.shape, theme().baseline_adjustment());
|
||||
}
|
||||
for (auto& cell : _row) {
|
||||
cell.value->set_layout(context.transform(cell.shape, transform_command::level));
|
||||
}
|
||||
}
|
||||
|
||||
void draw(hi::draw_context const& context) noexcept override {
|
||||
if (mode() <= hi::widget_mode::invisible) {
|
||||
return;
|
||||
}
|
||||
for (auto& cell : _row) {
|
||||
cell.value->draw(context);
|
||||
}
|
||||
}
|
||||
protected:
|
||||
// This function MUST be overridden when a widget has children.
|
||||
//
|
||||
// The order of the children returned is used for determining the next widget for
|
||||
// keyboard navigation.
|
||||
//
|
||||
// The allocator argument should not be used by the function, it is used by the caller
|
||||
// to allocate the co-routine's frame on the stack.
|
||||
[[nodiscard]] hi::generator<widget_intf &> children(bool include_invisible) noexcept override
|
||||
{
|
||||
// This function is often written as a co-routine that yields a pointer to each of its children.
|
||||
for (auto& cell : _row) {
|
||||
co_yield *cell.value;
|
||||
}
|
||||
_slot.set_content(vertical);
|
||||
}
|
||||
private:
|
||||
// Child widgets are owned by their parent.
|
||||
hi::text_widget* _text_widget;
|
||||
volume_bar* _l_volume_bar;
|
||||
volume_bar* _r_volume_bar;
|
||||
slider<float>* _volume_control;
|
||||
|
||||
hi::box_constraints _label_constraints;
|
||||
hi::box_constraints _l_volume_bar_constraints;
|
||||
// hi::box_constraints _r_volume_bar_constraints;
|
||||
hi::box_shape _label_shape;
|
||||
hi::box_shape _l_volume_bar_shape;
|
||||
// hi::box_shape _r_volume_bar_shape;
|
||||
|
||||
hi::column_layout<std::unique_ptr<widget>> _row;
|
||||
observer<label> _label;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user