Splitter element. Fixes #10.

This commit is contained in:
Ben Vanik 2015-07-08 22:11:41 -07:00
parent 30adbc89f3
commit 20a0dee815
11 changed files with 401 additions and 11 deletions

View File

@ -49,6 +49,7 @@
<ClInclude Include="src\el\elements\separator.h" />
<ClInclude Include="src\el\elements\slider.h" />
<ClInclude Include="src\el\elements\spin_box.h" />
<ClInclude Include="src\el\elements\split_container.h" />
<ClInclude Include="src\el\elements\tab_container.h" />
<ClInclude Include="src\el\elements\text_box.h" />
<ClInclude Include="src\el\elements\toggle_container.h" />
@ -151,6 +152,7 @@
<ClCompile Include="src\el\elements\separator.cc" />
<ClCompile Include="src\el\elements\slider.cc" />
<ClCompile Include="src\el\elements\spin_box.cc" />
<ClCompile Include="src\el\elements\split_container.cc" />
<ClCompile Include="src\el\elements\tab_container.cc" />
<ClCompile Include="src\el\elements\text_box.cc" />
<ClCompile Include="src\el\elements\toggle_container.cc" />

View File

@ -304,6 +304,9 @@
<ClInclude Include="src\el\io\win32_res_file_system.h">
<Filter>src\el\io</Filter>
</ClInclude>
<ClInclude Include="src\el\elements\split_container.h">
<Filter>src\el\elements</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="src">
@ -671,6 +674,9 @@
<ClCompile Include="src\el\io\win32_res_file_system.cc">
<Filter>src\el\io</Filter>
</ClCompile>
<ClCompile Include="src\el\elements\split_container.cc">
<Filter>src\el\elements</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Text Include="src\el\testing\data\glass.txt">

View File

@ -438,6 +438,20 @@ elements
SliderFgY
clone SliderFgX
SplitContainer
bitmap container.png
type stretch border
cut 12
expand 6
padding 10
SplitContainer.pane
clone SplitContainer
SplitContainer.divider
bitmap window_mover_bg_tile.png
type tile
expand -1
padding 4
Resizer
bitmap resizer.png
SpinBox

View File

@ -39,6 +39,7 @@ void RegisterBuiltinElementInflaters() {
Mover::RegisterInflater();
Resizer::RegisterInflater();
Dimmer::RegisterInflater();
SplitContainer::RegisterInflater();
}
} // namespace el

View File

@ -36,6 +36,7 @@
#include "el/elements/separator.h"
#include "el/elements/slider.h"
#include "el/elements/spin_box.h"
#include "el/elements/split_container.h"
#include "el/elements/tab_container.h"
#include "el/elements/text_box.h"
#include "el/elements/toggle_container.h"

View File

@ -93,13 +93,12 @@ bool Slider::OnEvent(const Event& ev) {
if (ev.special_key == SpecialKey::kLeft ||
ev.special_key == SpecialKey::kUp) {
set_double_value(double_value() - step);
return true;
} else if (ev.special_key == SpecialKey::kRight ||
ev.special_key == SpecialKey::kDown) {
set_double_value(double_value() + step);
} else {
return Element::OnEvent(ev);
return true;
}
return true;
} else if (ev.type == EventType::kKeyUp) {
if (ev.special_key == SpecialKey::kLeft ||
ev.special_key == SpecialKey::kUp ||

View File

@ -0,0 +1,253 @@
/**
******************************************************************************
* Elemental Forms : a lightweight user interface framework *
******************************************************************************
* ©2015 Ben Vanik. All rights reserved. Released under the BSD license. *
* Portions ©2011-2015 Emil Segerås: https://github.com/fruxo/turbobadger *
******************************************************************************
*/
#include "el/elements/split_container.h"
#include "el/parsing/element_inflater.h"
#include "el/util/math.h"
namespace el {
namespace elements {
constexpr int kSplitterSize = 10;
void SplitContainer::RegisterInflater() {
EL_REGISTER_ELEMENT_INFLATER(SplitContainer, Value::Type::kInt,
ElementZ::kTop);
}
SplitContainer::SplitContainer() {
set_gravity(Gravity::kAll);
AddChild(&first_pane_);
AddChild(&divider_);
AddChild(&second_pane_);
first_pane_.set_background_skin(TBIDC("SplitContainer.pane"));
first_pane_.set_gravity(Gravity::kAll);
second_pane_.set_background_skin(TBIDC("SplitContainer.pane"));
second_pane_.set_gravity(Gravity::kAll);
divider_.set_background_skin(TBIDC("SplitContainer.divider"));
divider_.set_gravity(Gravity::kAll);
divider_.set_focusable(true);
}
SplitContainer::~SplitContainer() {
RemoveChild(&first_pane_);
RemoveChild(&second_pane_);
RemoveChild(&divider_);
}
void SplitContainer::OnInflate(const parsing::InflateInfo& info) {
set_gravity(Gravity::kAll);
if (auto axis_string = info.node->GetValueString("axis", nullptr)) {
set_axis(from_string(axis_string, axis()));
}
if (auto fixed_string = info.node->GetValueString("fixed", nullptr)) {
set_fixed_pane(from_string(fixed_string, fixed_pane()));
}
set_limits(info.node->GetValueInt("min", min_value()),
info.node->GetValueInt("max", max_value()));
initial_value_ = info.node->GetValueInt("value", util::kInvalidDimension);
Element::OnInflate(info);
}
void SplitContainer::set_axis(Axis axis) {
if (axis == axis_) return;
axis_ = axis;
InvalidateLayout(InvalidationMode::kTargetOnly);
}
void SplitContainer::set_fixed_pane(FixedPane fixed_pane) {
if (fixed_pane == fixed_pane_) return;
fixed_pane_ = fixed_pane;
InvalidateLayout(InvalidationMode::kTargetOnly);
}
void SplitContainer::set_value(int value) {
int clamped_value =
util::Clamp(value, computed_min_value(), computed_max_value());
if (clamped_value == value_) return;
value_ = clamped_value;
InvalidateLayout(InvalidationMode::kTargetOnly);
Event ev(EventType::kChanged);
InvokeEvent(ev);
}
int SplitContainer::computed_min_value() const {
return min_value_ == util::kInvalidDimension ? 0 : min_value_;
}
int SplitContainer::computed_max_value() const {
return axis_ == Axis::kX ? std::max(0, rect().h - kSplitterSize)
: std::max(0, rect().w - kSplitterSize);
}
void SplitContainer::set_limits(int min_value, int max_value) {
if (min_value != util::kInvalidDimension &&
max_value != util::kInvalidDimension) {
min_value = std::min(min_value, max_value);
}
if (min_value == min_value_ && max_value == max_value_) {
return;
}
min_value_ = min_value;
max_value_ = max_value;
set_value(value_);
}
void SplitContainer::OnProcess() {
SizeConstraints sc(rect().w, rect().h);
ValidateLayout(sc);
}
void SplitContainer::OnResized(int old_w, int old_h) {
InvalidateLayout(InvalidationMode::kTargetOnly);
SizeConstraints sc(rect().w, rect().h);
ValidateLayout(sc);
}
void SplitContainer::OnChildAdded(Element* child) {
if (child == &first_pane_ || child == &divider_ || child == &second_pane_) {
return;
}
RemoveChild(child, InvokeInfo::kNoCallbacks);
if (!first_pane_.first_child()) {
first_pane_.AddChild(child);
} else {
second_pane_.AddChild(child);
}
}
PreferredSize SplitContainer::OnCalculatePreferredContentSize(
const SizeConstraints& constraints) {
PreferredSize ps = Element::OnCalculatePreferredContentSize(constraints);
if (axis_ == Axis::kX) {
ps.min_h += computed_min_value();
} else {
ps.min_w += computed_min_value();
}
ps.pref_w = std::max(ps.min_w, ps.pref_w);
ps.pref_h = std::max(ps.min_h, ps.pref_h);
return ps;
}
void SplitContainer::ValidateLayout(const SizeConstraints& constraints) {
const Rect padding_rect = this->padding_rect();
auto inner_sc = constraints.ConstrainByPadding(rect().w - padding_rect.w,
rect().h - padding_rect.h);
// First layout since inflated?
if (initial_value_ != util::kInvalidDimension) {
set_value(initial_value_);
initial_value_ = util::kInvalidDimension;
}
Rect first_rect;
Rect divider_rect;
Rect second_rect;
if (axis_ == Axis::kX) {
first_rect.x = divider_rect.x = second_rect.x = 0;
first_rect.w = divider_rect.w = second_rect.w = inner_sc.available_w;
if (fixed_pane_ == FixedPane::kFirst) {
first_rect.y = 0;
first_rect.h = value_;
divider_rect.y = value_;
divider_rect.h = kSplitterSize;
second_rect.y = value_ + kSplitterSize;
second_rect.h = inner_sc.available_h - value_ - kSplitterSize;
} else {
first_rect.y = 0;
first_rect.h = inner_sc.available_h - value_ - kSplitterSize;
divider_rect.y = inner_sc.available_h - value_ - kSplitterSize;
divider_rect.h = kSplitterSize;
second_rect.y = inner_sc.available_h - value_;
second_rect.h = value_;
}
} else {
first_rect.y = divider_rect.y = second_rect.y = 0;
first_rect.h = divider_rect.h = second_rect.h = inner_sc.available_h;
if (fixed_pane_ == FixedPane::kFirst) {
first_rect.x = 0;
first_rect.w = value_;
divider_rect.x = value_;
divider_rect.w = kSplitterSize;
second_rect.x = value_ + kSplitterSize;
second_rect.w = inner_sc.available_w - value_ - kSplitterSize;
} else {
first_rect.x = 0;
first_rect.w = inner_sc.available_w - value_ - kSplitterSize;
divider_rect.x = inner_sc.available_w - value_ - kSplitterSize;
divider_rect.w = kSplitterSize;
second_rect.x = inner_sc.available_w - value_;
second_rect.w = value_;
}
}
first_pane_.set_rect(first_rect);
divider_.set_rect(divider_rect);
second_pane_.set_rect(second_rect);
}
bool SplitContainer::Divider::OnEvent(const Event& ev) {
auto parent = reinterpret_cast<SplitContainer*>(this->parent());
if (!parent) {
return false;
}
if (ev.type == EventType::kPointerMove && captured_element == this) {
int dx = ev.target_x - pointer_down_element_x;
int dy = ev.target_y - pointer_down_element_y;
Rect r = rect().Offset(dx, dy);
if (parent->axis() == Axis::kX) {
parent->set_value(parent->fixed_pane() == FixedPane::kFirst
? r.y
: -r.y + parent->rect().h);
} else {
parent->set_value(parent->fixed_pane() == FixedPane::kFirst
? r.x
: -r.x + parent->rect().w);
}
return true;
} else if (ev.type == EventType::kPointerDown && ev.count == 2) {
if (parent->value() == parent->computed_min_value()) {
parent->set_value(parent->computed_max_value());
} else {
parent->set_value(parent->computed_min_value());
}
return true;
} else if (ev.type == EventType::kKeyDown) {
int step = kSplitterSize;
if ((ev.modifierkeys & ModifierKeys::kShift) == ModifierKeys::kShift) {
step *= 4;
}
if (parent->fixed_pane() == FixedPane::kSecond) {
step = -step;
}
if (ev.special_key == SpecialKey::kLeft ||
ev.special_key == SpecialKey::kUp) {
parent->set_value(parent->value() - step);
return true;
} else if (ev.special_key == SpecialKey::kRight ||
ev.special_key == SpecialKey::kDown) {
parent->set_value(parent->value() + step);
return true;
}
} else if (ev.type == EventType::kKeyUp) {
if (ev.special_key == SpecialKey::kLeft ||
ev.special_key == SpecialKey::kUp ||
ev.special_key == SpecialKey::kRight ||
ev.special_key == SpecialKey::kDown) {
return true;
}
}
return Element::OnEvent(ev);
}
} // namespace elements
} // namespace el

View File

@ -0,0 +1,90 @@
/**
******************************************************************************
* Elemental Forms : a lightweight user interface framework *
******************************************************************************
* ©2015 Ben Vanik. All rights reserved. Released under the BSD license. *
* Portions ©2011-2015 Emil Segerås: https://github.com/fruxo/turbobadger *
******************************************************************************
*/
#ifndef EL_ELEMENTS_SPLIT_CONTAINER_H_
#define EL_ELEMENTS_SPLIT_CONTAINER_H_
#include "el/element.h"
#include "el/elements/box.h"
namespace el {
namespace elements {
// Specifies how resizing of the split container is handled.
enum class FixedPane {
// Value controls the first pane's size and the first pane will remain fixed
// during resizing.
kFirst,
// Value controls the second pane's size and the second pane will remain fixed
// during resizing.
kSecond,
};
MAKE_ORDERED_ENUM_STRING_UTILS(FixedPane, "first", "second");
// An element that provides two containers resizable with a divider.
class SplitContainer : public Element {
public:
TBOBJECT_SUBCLASS(SplitContainer, Element);
static void RegisterInflater();
SplitContainer();
~SplitContainer() override;
Axis axis() const { return axis_; }
// Sets the axis the divider splits across. X is horizontal, Y is vertical.
void set_axis(Axis axis);
FixedPane fixed_pane() const { return fixed_pane_; }
// Sets how resizing of the split container is handled.
void set_fixed_pane(FixedPane fixed_pane);
int value() override { return value_; }
// Sets the split % of this element.
void set_value(int value) override;
int min_value() const { return min_value_; }
int max_value() const { return max_value_; }
int computed_min_value() const;
int computed_max_value() const;
// Sets the min, max value for the splitter.
void set_limits(int min_value, int max_value);
void OnInflate(const parsing::InflateInfo& info) override;
void OnProcess() override;
void OnResized(int old_w, int old_h) override;
void OnChildAdded(Element* child) override;
PreferredSize OnCalculatePreferredContentSize(
const SizeConstraints& constraints) override;
Element* first_pane() { return &first_pane_; }
Element* second_pane() { return &second_pane_; }
private:
class Divider : public Element {
public:
bool OnEvent(const Event& ev) override;
};
void ValidateLayout(const SizeConstraints& constraints);
Axis axis_ = Axis::kX;
FixedPane fixed_pane_ = FixedPane::kFirst;
int initial_value_ = util::kInvalidDimension;
int value_ = 0;
int min_value_ = 0;
int max_value_ = util::kInvalidDimension;
Element first_pane_;
Element second_pane_;
Divider divider_;
};
} // namespace elements
} // namespace el
#endif // EL_ELEMENTS_SPLIT_CONTAINER_H_

View File

@ -50,7 +50,7 @@ class Window : public Element {
bool is_active() const;
// Activates this window if it's not already activated.
// This will deactivate any currently activated window..
// This will deactivate any currently activated window.
// This will automatically call EnsureFocus to restore/set focus to this
// window.
void Activate();

View File

@ -12,10 +12,15 @@ LayoutBox
Slider
value 0.5
ScrollBar
TextBox
multiline 1
readonly 1
TextBox
text Hello world\nHello world
adapt-to-content 1
multiline 1
SplitContainer
gravity all
value 40
TextBox
gravity all
multiline 1
readonly 1
TextBox
gravity all
text Hello world\nHello world
adapt-to-content 1
multiline 1

View File

@ -10,6 +10,7 @@ TabContainer
Button: text: "Spinner"
IconBox: skin: Icon16
Button: text: code
Button: text: "Splitter"
LayoutBox: axis: y
Label: text: "Tab alignment:"
LayoutBox
@ -45,3 +46,21 @@ TabContainer
multiline 1
styling 1
gravity all
LayoutBox
gravity all
distribution available
SplitContainer
gravity all
value 40
min 40
fixed first
TextBox
gravity all
multiline 1
readonly 1
TextBox
gravity all
text Hello world\nHello world
adapt-to-content 1
multiline 1