1731 lines
42 KiB
C++
1731 lines
42 KiB
C++
/*
|
|
* A Tabbar Implementation
|
|
* Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com)
|
|
*
|
|
* Distributed under the Boost Software License, Version 1.0.
|
|
* (See accompanying file LICENSE_1_0.txt or copy at
|
|
* http://www.boost.org/LICENSE_1_0.txt)
|
|
*
|
|
* @file: nana/gui/widgets/tabbar.hpp
|
|
*/
|
|
#include <nana/gui/widgets/tabbar.hpp>
|
|
#include <nana/gui/widgets/menu.hpp>
|
|
#include <nana/paint/text_renderer.hpp>
|
|
#include <nana/gui/element.hpp>
|
|
#include <stdexcept>
|
|
#include <list>
|
|
|
|
namespace nana
|
|
{
|
|
namespace drawerbase
|
|
{
|
|
namespace tabbar
|
|
{
|
|
using native_string_type = ::nana::detail::native_string_type;
|
|
struct item_t
|
|
{
|
|
window relative{nullptr};
|
|
paint::image img;
|
|
native_string_type text;
|
|
any value;
|
|
|
|
::nana::color bgcolor;
|
|
::nana::color fgcolor;
|
|
|
|
item_t() = default;
|
|
|
|
item_t(native_string_type&& text, any && value)
|
|
: text(std::move(text)), value(std::move(value))
|
|
{}
|
|
};
|
|
|
|
class def_renderer
|
|
: public item_renderer
|
|
{
|
|
private:
|
|
virtual void background(graph_reference graph, const nana::rectangle&, const ::nana::color& bgcolor)
|
|
{
|
|
if(bgcolor_ != bgcolor)
|
|
{
|
|
bgcolor_ = bgcolor;
|
|
|
|
dark_bgcolor_ = bgcolor.blend(colors::black, 0.9);
|
|
blcolor_ = bgcolor.blend(colors::black, 0.5);
|
|
ilcolor_ = bgcolor.blend(colors::white, 0.9);
|
|
}
|
|
|
|
graph.rectangle(true, bgcolor);
|
|
}
|
|
|
|
virtual void item(graph_reference graph, const item_t& m, bool active, state_t sta)
|
|
{
|
|
const nana::rectangle & r = m.r;
|
|
color bgcolor;
|
|
color blcolor;
|
|
color dark_bgcolor;
|
|
|
|
if(m.bgcolor.invisible())
|
|
{
|
|
bgcolor = bgcolor_;
|
|
blcolor = blcolor_;
|
|
dark_bgcolor = dark_bgcolor_;
|
|
}
|
|
else
|
|
{
|
|
bgcolor = m.bgcolor;
|
|
blcolor = m.bgcolor.blend(colors::black, 0.5);
|
|
dark_bgcolor = m.bgcolor.blend(colors::black, 0.9);
|
|
}
|
|
|
|
auto round_r = r;
|
|
round_r.height += 2;
|
|
graph.round_rectangle(round_r, 3, 3, blcolor, true, colors::white);
|
|
|
|
auto beg = bgcolor;
|
|
auto end = dark_bgcolor;
|
|
|
|
if(active)
|
|
{
|
|
if (m.bgcolor.invisible())
|
|
beg = ilcolor_;
|
|
else
|
|
beg = m.bgcolor.blend(colors::white, 0.5);
|
|
end = bgcolor;
|
|
}
|
|
|
|
if (sta == item_renderer::highlight)
|
|
beg = beg.blend(colors::white, 0.5);
|
|
|
|
graph.gradual_rectangle(round_r.pare_off(2), beg, end, true);
|
|
}
|
|
|
|
virtual void add(graph_reference graph, const nana::rectangle& r, state_t sta)
|
|
{
|
|
int x = r.x + (static_cast<int>(r.width) - 14) / 2;
|
|
int y = r.y + (static_cast<int>(r.height) - 14) / 2;
|
|
|
|
::nana::color clr;
|
|
|
|
switch(sta)
|
|
{
|
|
case item_renderer::highlight:
|
|
clr = colors::white; break;
|
|
case item_renderer::press:
|
|
clr = static_cast<color_rgb>(0xA0A0A0); break;
|
|
case item_renderer::disable:
|
|
clr = static_cast<color_rgb>(0x808080); break;
|
|
default:
|
|
clr = static_cast<color_rgb>(0xF0F0F0);
|
|
}
|
|
graph.rectangle(r, true, bgcolor_);
|
|
facade<element::cross> cross;
|
|
cross.draw(graph, {}, clr, { x, y, 14, 6 }, element_state::normal);
|
|
}
|
|
|
|
virtual void close(graph_reference graph, const nana::rectangle& r, state_t sta)
|
|
{
|
|
facade<element::x_icon> x_icon;
|
|
x_icon.draw(graph, {}, colors::black, { r.x + static_cast<int>(r.width - 16) / 2, r.y + static_cast<int>(r.height - 16) / 2, 16, 16 }, element_state::normal);
|
|
if(item_renderer::highlight == sta)
|
|
graph.rectangle(r, false, static_cast<color_rgb>(0xa0a0a0));
|
|
}
|
|
|
|
virtual void close_fly(graph_reference graph, const nana::rectangle& r, bool active, state_t sta)
|
|
{
|
|
using namespace nana::paint;
|
|
::nana::color clr{ colors::black };
|
|
|
|
if (sta == item_renderer::highlight)
|
|
{
|
|
::nana::color bgcolor{ static_cast<color_rgb>(0xCCD2DD) };
|
|
::nana::color rect_clr{ static_cast<color_rgb>(0x9da3ab) };
|
|
graph.round_rectangle(r, 1, 1, rect_clr, false, {});
|
|
nana::rectangle draw_r(r);
|
|
graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.8));
|
|
graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.4));
|
|
graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.2));
|
|
}
|
|
else if (!active)
|
|
clr = static_cast<color_rgb>(0x9299a4);
|
|
|
|
facade<element::x_icon> x_icon;
|
|
x_icon.draw(graph, {}, colors::black, { r.x + static_cast<int>(r.width - 16) / 2, r.y + static_cast<int>(r.height - 16) / 2, 16, 16 }, element_state::normal);
|
|
|
|
}
|
|
|
|
virtual void back(graph_reference graph, const nana::rectangle& r, state_t sta)
|
|
{
|
|
_m_draw_arrow(graph, r, sta, direction::west);
|
|
}
|
|
|
|
virtual void next(graph_reference graph, const nana::rectangle& r, state_t sta)
|
|
{
|
|
_m_draw_arrow(graph, r, sta, direction::east);
|
|
}
|
|
|
|
virtual void list(graph_reference graph, const nana::rectangle& r, state_t sta)
|
|
{
|
|
_m_draw_arrow(graph, r, sta, direction::south);
|
|
}
|
|
private:
|
|
void _m_draw_arrow(graph_reference graph, const nana::rectangle& r, state_t sta, ::nana::direction dir)
|
|
{
|
|
facade<element::arrow> arrow("solid_triangle");
|
|
arrow.direction(dir);
|
|
colors fgcolor = colors::black;
|
|
if (item_renderer::disable == sta)
|
|
{
|
|
arrow.switch_to("hollow_triangle");
|
|
fgcolor = colors::gray;
|
|
}
|
|
auto arrow_r = r;
|
|
arrow_r.x += static_cast<int>(arrow_r.width - 16) / 2;
|
|
arrow_r.y += static_cast<int>(arrow_r.height - 16) / 2;
|
|
arrow_r.width = arrow_r.height = 16;
|
|
arrow.draw(graph, bgcolor_, fgcolor, arrow_r, element_state::normal);
|
|
|
|
if(item_renderer::highlight == sta)
|
|
graph.rectangle(r, false, colors::dark_gray);
|
|
}
|
|
private:
|
|
::nana::color bgcolor_;
|
|
::nana::color dark_bgcolor_;
|
|
::nana::color blcolor_;
|
|
::nana::color ilcolor_;
|
|
};
|
|
|
|
class toolbox
|
|
{
|
|
struct button_tag
|
|
{
|
|
bool visible;
|
|
bool enable;
|
|
};
|
|
public:
|
|
enum button_t{ButtonAdd, ButtonScrollBack, ButtonScrollNext, ButtonList, ButtonClose, ButtonSize};
|
|
|
|
toolbox()
|
|
: close_fly_(false)
|
|
{
|
|
for(int i = ButtonAdd; i < ButtonSize; ++i)
|
|
{
|
|
buttons_[i].visible = true;
|
|
buttons_[i].enable = true;
|
|
}
|
|
buttons_[0].enable = false;
|
|
buttons_[3].enable = false;
|
|
buttons_[4].enable = false;
|
|
}
|
|
|
|
nana::rectangle area(button_t btn, unsigned height) const
|
|
{
|
|
nana::rectangle r(-1, 0, 0, 0);
|
|
if((btn == ButtonAdd) || (btn == ButtonClose && close_fly_))
|
|
return r;
|
|
|
|
int x = 0;
|
|
for(int i = ButtonScrollBack; i < ButtonSize; ++i)
|
|
{
|
|
if(btn != i)
|
|
{
|
|
if(i != ButtonAdd && buttons_[i].visible && buttons_[i].enable)
|
|
x += item_pixels();
|
|
}
|
|
else
|
|
{
|
|
r.x = x;
|
|
r.width = item_pixels();
|
|
r.height = height;
|
|
return r;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
bool renderable(button_t btn) const
|
|
{
|
|
if(ButtonAdd <= btn && btn < ButtonSize)
|
|
{
|
|
if((btn == ButtonClose) && close_fly_)
|
|
return false;
|
|
return (buttons_[btn].visible && buttons_[btn].enable);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool visible(button_t btn, bool vs)
|
|
{
|
|
if(buttons_[btn].visible != vs)
|
|
{
|
|
buttons_[btn].visible = vs;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool visible(button_t btn) const
|
|
{
|
|
return buttons_[btn].visible;
|
|
}
|
|
|
|
bool close_fly(bool fly)
|
|
{
|
|
if(close_fly_ != fly)
|
|
{
|
|
close_fly_ = fly;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool close_fly() const
|
|
{
|
|
return close_fly_;
|
|
}
|
|
|
|
bool enable(button_t btn) const
|
|
{
|
|
return buttons_[btn].enable;
|
|
}
|
|
|
|
bool enable(button_t btn, bool enb)
|
|
{
|
|
if(buttons_[btn].enable != enb)
|
|
{
|
|
buttons_[btn].enable = enb;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
unsigned width() const
|
|
{
|
|
unsigned pixels = 0;
|
|
for(int i = ButtonScrollBack; i < ButtonSize; ++i)
|
|
{
|
|
if(renderable(static_cast<button_t>(i)))
|
|
{
|
|
if((i != ButtonClose) || (close_fly_ == false))
|
|
pixels += item_pixels();
|
|
}
|
|
}
|
|
return pixels;
|
|
}
|
|
|
|
unsigned item_pixels() const
|
|
{
|
|
return 18;
|
|
}
|
|
|
|
button_t which(int x) const
|
|
{
|
|
for(int i = ButtonAdd; i < ButtonSize; ++i)
|
|
{
|
|
if((i == ButtonClose && close_fly_) || (i == ButtonAdd))
|
|
continue;
|
|
|
|
if(renderable(static_cast<button_t>(i)))
|
|
{
|
|
if(0 <= x && x < static_cast<int>(item_pixels()))
|
|
return static_cast<button_t>(i);
|
|
else
|
|
x -= static_cast<int>(item_pixels());
|
|
}
|
|
}
|
|
return ButtonSize;
|
|
}
|
|
private:
|
|
bool close_fly_;
|
|
button_tag buttons_[ButtonSize];
|
|
};
|
|
|
|
//
|
|
class layouter
|
|
{
|
|
public:
|
|
typedef std::list<item_t>::iterator iterator;
|
|
typedef std::list<item_t>::const_iterator const_iterator;
|
|
|
|
nana::any& at(std::size_t i)
|
|
{
|
|
if(i < list_.size())
|
|
return at_no_bound_check(i);
|
|
throw std::out_of_range("invalid position of tabbar");
|
|
}
|
|
|
|
iterator iterator_at(std::size_t pos)
|
|
{
|
|
auto i = list_.begin();
|
|
std::advance(i, pos);
|
|
return i;
|
|
}
|
|
|
|
const_iterator iterator_at(std::size_t pos) const
|
|
{
|
|
auto i = list_.cbegin();
|
|
std::advance(i, pos);
|
|
return i;
|
|
}
|
|
|
|
nana::any& at_no_bound_check(std::size_t pos)
|
|
{
|
|
return iterator_at(pos)->value;
|
|
}
|
|
|
|
const nana::any& at(std::size_t pos) const
|
|
{
|
|
if(pos < list_.size())
|
|
return at_no_bound_check(pos);
|
|
throw std::out_of_range("invalid position of tabbar");
|
|
}
|
|
|
|
const nana::any& at_no_bound_check(std::size_t pos) const
|
|
{
|
|
return iterator_at(pos)->value;
|
|
}
|
|
|
|
toolbox & toolbox_object()
|
|
{
|
|
return toolbox_;
|
|
}
|
|
|
|
window widget_handle() const
|
|
{
|
|
return basis_.wd;
|
|
}
|
|
|
|
void attach(window wd, nana::paint::graphics& graph)
|
|
{
|
|
basis_.wd = wd;
|
|
basis_.graph = &graph;
|
|
}
|
|
|
|
void detach()
|
|
{
|
|
basis_.graph = 0;
|
|
}
|
|
|
|
const pat::cloneable<item_renderer> & ext_renderer() const
|
|
{
|
|
return basis_.renderer;
|
|
}
|
|
|
|
void ext_renderer(const pat::cloneable<item_renderer>& ir)
|
|
{
|
|
basis_.renderer = ir;
|
|
}
|
|
|
|
void event_agent(event_agent_interface* evt)
|
|
{
|
|
evt_agent_ = evt;
|
|
}
|
|
|
|
void insert(std::size_t pos, native_string_type&& text, nana::any&& value)
|
|
{
|
|
if (pos >= list_.size())
|
|
pos = list_.size();
|
|
|
|
list_.emplace(iterator_at(pos), std::move(text), std::move(value));
|
|
this->activate(pos);
|
|
render();
|
|
}
|
|
|
|
std::size_t length() const
|
|
{
|
|
return list_.size();
|
|
}
|
|
|
|
bool erase(std::size_t pos)
|
|
{
|
|
if(pos < list_.size())
|
|
{
|
|
bool close_attach = true;
|
|
if ((nullptr == evt_agent_) || evt_agent_->removed(pos, close_attach))
|
|
{
|
|
if (close_attach)
|
|
API::close_window(iterator_at(pos)->relative);
|
|
else
|
|
API::show_window(iterator_at(pos)->relative, false);
|
|
list_.erase(iterator_at(pos));
|
|
_m_adjust();
|
|
|
|
if(pos < basis_.active)
|
|
{
|
|
--basis_.active;
|
|
if(basis_.scroll_pixels > basis_.item_pixels)
|
|
basis_.scroll_pixels -= basis_.item_pixels;
|
|
else
|
|
basis_.scroll_pixels = 0;
|
|
}
|
|
else
|
|
{
|
|
auto count = list_.size();
|
|
if (pos == count)
|
|
basis_.active = pos = count - 1;
|
|
|
|
count *= basis_.item_pixels;
|
|
if (count > static_cast<unsigned>(_m_itembar_right()))
|
|
basis_.scroll_pixels = static_cast<unsigned>(count)-static_cast<unsigned>(_m_itembar_right());
|
|
else
|
|
basis_.scroll_pixels = 0;
|
|
}
|
|
|
|
if (basis_.active != ::nana::npos)
|
|
API::show_window(iterator_at(basis_.active)->relative, true);
|
|
if(evt_agent_)
|
|
evt_agent_->activated(basis_.active);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void render()
|
|
{
|
|
_m_adjust();
|
|
_m_render();
|
|
}
|
|
|
|
bool press()
|
|
{
|
|
trace_.state = item_renderer::press;
|
|
return (trace_.what != trace_.null);
|
|
}
|
|
|
|
bool active_by_trace()
|
|
{
|
|
return ((trace_.what == trace_.item) && (trace_.item_part != trace_.close)? activate(trace_.u.index) : false);
|
|
}
|
|
|
|
bool release()
|
|
{
|
|
trace_.state = item_renderer::highlight;
|
|
return true;
|
|
}
|
|
|
|
bool leave()
|
|
{
|
|
trace_.state = item_renderer::normal;
|
|
if(trace_.what != trace_.null)
|
|
{
|
|
trace_.what = trace_.null;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void track()
|
|
{
|
|
int left, right;
|
|
if(_m_item_pos(basis_.active, left, right))
|
|
{
|
|
if(left < 0)
|
|
basis_.scroll_pixels -= static_cast<unsigned>(-left);
|
|
else if(right > _m_itembar_right())
|
|
basis_.scroll_pixels += static_cast<unsigned>(right - _m_itembar_right());
|
|
}
|
|
}
|
|
|
|
bool trace(int x, int y)
|
|
{
|
|
trace_.state = item_renderer::highlight;
|
|
if(basis_.graph == 0) return false;
|
|
|
|
int ibar_end = _m_itembar_right();
|
|
trace_tag::item_t item_part = trace_.item_part;
|
|
std::size_t index = _m_where_itembar(x, y, ibar_end);
|
|
if(index != npos)
|
|
{
|
|
if((trace_.what != trace_.item) || (trace_.u.index != index) || (item_part != trace_.item_part))
|
|
{
|
|
trace_.what = trace_.item;
|
|
trace_.u.index = index;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if(toolbox_.renderable(toolbox_.ButtonAdd))
|
|
{
|
|
if(ibar_end <= x && x < ibar_end + static_cast<int>(toolbox_.item_pixels()))
|
|
{
|
|
if((trace_.what != trace_.toolbox) || (trace_.u.button != toolbox::ButtonAdd))
|
|
{
|
|
trace_.what = trace_.toolbox;
|
|
trace_.u.button = toolbox::ButtonAdd;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int tbpos = _m_toolbox_pos();
|
|
if(tbpos <= x)
|
|
{
|
|
toolbox::button_t t = toolbox_.which(x - tbpos);
|
|
|
|
if(trace_.what != trace_.toolbox || trace_.u.button != t)
|
|
{
|
|
trace_.what = trace_.toolbox;
|
|
trace_.u.button = t;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if(trace_.what != trace_.null)
|
|
{
|
|
trace_.what = trace_.null;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool activate(std::size_t pos)
|
|
{
|
|
if(pos < list_.size() && (pos != basis_.active))
|
|
{
|
|
auto & tab_act = *iterator_at(pos);
|
|
API::show_window(tab_act.relative, true);
|
|
if (basis_.active < list_.size())
|
|
{
|
|
auto & tab_deact = *iterator_at(basis_.active);
|
|
|
|
//Don't hide the relative window if it is equal to active relative window.
|
|
//The tabbar allows a window to be attached to multiple tabs(pass false to DropOther of attach method)
|
|
if (tab_deact.relative != tab_act.relative)
|
|
API::show_window(tab_deact.relative, false);
|
|
}
|
|
|
|
basis_.active = pos;
|
|
track();
|
|
if(evt_agent_)
|
|
evt_agent_->activated(pos);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::size_t active() const
|
|
{
|
|
return basis_.active;
|
|
}
|
|
|
|
window attach(std::size_t pos, window wd, bool drop_other)
|
|
{
|
|
if (pos >= list_.size())
|
|
throw std::out_of_range("tabbar: invalid position");
|
|
|
|
auto & tab = *iterator_at(pos);
|
|
auto old = tab.relative;
|
|
|
|
if (drop_other)
|
|
{
|
|
//Drop the relative windows which are equal to wd.
|
|
for (auto & t : list_)
|
|
{
|
|
if (wd == t.relative)
|
|
t.relative = nullptr;
|
|
}
|
|
}
|
|
|
|
tab.relative = wd;
|
|
API::show_window(wd, basis_.active == pos);
|
|
return old;
|
|
}
|
|
|
|
bool tab_color(std::size_t pos, bool is_bgcolor, const ::nana::color& clr)
|
|
{
|
|
if (pos >= list_.size())
|
|
throw std::out_of_range("tabbar: invalid position");
|
|
|
|
auto & m = *iterator_at(pos);
|
|
auto & m_clr = (is_bgcolor ? m.bgcolor : m.fgcolor);
|
|
if (m_clr != clr)
|
|
{
|
|
m_clr = clr;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void tab_image(std::size_t pos, const nana::paint::image& img)
|
|
{
|
|
if (pos >= list_.size())
|
|
throw std::out_of_range("tabbar: invalid position");
|
|
|
|
auto & m = *iterator_at(pos);
|
|
if(img)
|
|
m.img = img;
|
|
else
|
|
m.img.close();
|
|
}
|
|
|
|
bool text(std::size_t pos, const native_string_type& str)
|
|
{
|
|
if (pos >= list_.size())
|
|
throw std::out_of_range("tabbar: invalid position");
|
|
|
|
auto & m = *iterator_at(pos);
|
|
if(m.text != str)
|
|
{
|
|
m.text = str;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
native_string_type text(std::size_t pos) const
|
|
{
|
|
if (pos >= list_.size())
|
|
throw std::out_of_range("tabbar: invalid position");
|
|
|
|
return iterator_at(pos)->text;
|
|
}
|
|
|
|
bool toolbox_answer(const arg_mouse& arg)
|
|
{
|
|
if(trace_.what == trace_.toolbox)
|
|
{
|
|
if(toolbox_.renderable(trace_.u.button))
|
|
{
|
|
switch(trace_.u.button)
|
|
{
|
|
case toolbox::ButtonAdd:
|
|
if(event_code::mouse_up == arg.evt_code)
|
|
return _m_add_tab(npos);
|
|
break;
|
|
case toolbox::ButtonScrollBack:
|
|
if(event_code::mouse_down == arg.evt_code)
|
|
return _m_scroll(true);
|
|
break;
|
|
case toolbox::ButtonScrollNext:
|
|
if(event_code::mouse_down == arg.evt_code)
|
|
return _m_scroll(false);
|
|
break;
|
|
case toolbox::ButtonList:
|
|
if (event_code::mouse_down == arg.evt_code)
|
|
{
|
|
_m_open_menulister();
|
|
return true;
|
|
}
|
|
break;
|
|
case toolbox::ButtonClose:
|
|
if (event_code::mouse_up == arg.evt_code)
|
|
{
|
|
if(this->erase(basis_.active))
|
|
{
|
|
track();
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if((trace_.what == trace_.item) && (trace_.item_part == trace_.close))
|
|
{
|
|
if(event_code::mouse_up == arg.evt_code)
|
|
{
|
|
if(this->erase(trace_.u.index))
|
|
{
|
|
track();
|
|
trace(arg.pos.x, arg.pos.y);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
private: //Fundation
|
|
bool _m_nextable() const
|
|
{
|
|
return (basis_.scroll_pixels + _m_itembar_right() < basis_.item_pixels * list_.size());
|
|
}
|
|
|
|
bool _m_add_tab(std::size_t pos)
|
|
{
|
|
item_t m;
|
|
if((pos == npos) || (pos >= list_.size()))
|
|
{
|
|
this->list_.push_back(m);
|
|
pos = static_cast<unsigned>(list_.size() - 1);
|
|
}
|
|
else
|
|
list_.insert(iterator_at(pos), m);
|
|
|
|
basis_.active = pos;
|
|
if(evt_agent_)
|
|
{
|
|
evt_agent_->added(pos);
|
|
evt_agent_->activated(pos);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool _m_scroll(bool left)
|
|
{
|
|
if(left)
|
|
{
|
|
if(basis_.scroll_pixels)
|
|
{
|
|
unsigned i = basis_.scroll_pixels / basis_.item_pixels;
|
|
if(i > 1)
|
|
basis_.scroll_pixels = (i - 1) * basis_.item_pixels;
|
|
else
|
|
basis_.scroll_pixels = 0;
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto scale = static_cast<unsigned>(_m_itembar_right());
|
|
auto take = static_cast<unsigned>(list_.size() * basis_.item_pixels);
|
|
if(take > scale)
|
|
{
|
|
auto i = (basis_.scroll_pixels + scale) / basis_.item_pixels;
|
|
i += (basis_.scroll_pixels % basis_.item_pixels ? 2 : 1);
|
|
auto px = i * basis_.item_pixels;
|
|
|
|
if(px > take) px = take;
|
|
|
|
basis_.scroll_pixels = px - scale;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void _m_open_menulister()
|
|
{
|
|
menulister_.clear();
|
|
auto fn = [this](::nana::menu::item_proxy& ipx)
|
|
{
|
|
if (this->activate(ipx.index()))
|
|
API::refresh_window(basis_.wd);
|
|
};
|
|
|
|
for(auto & m : list_)
|
|
menulister_.append(to_utf8(m.text), fn);
|
|
|
|
auto r = toolbox_.area(toolbox_.ButtonList, basis_.graph->height());
|
|
r.x += _m_toolbox_pos();
|
|
menulister_.popup(basis_.wd, r.x, r.bottom());
|
|
}
|
|
|
|
//the begin pos of toolbox
|
|
int _m_toolbox_pos() const
|
|
{
|
|
int tbpos = static_cast<int>(basis_.graph->width()) - static_cast<int>(_m_toolbox_pixels());
|
|
return (tbpos < 0 ? 0 : tbpos);
|
|
}
|
|
|
|
unsigned _m_toolbox_pixels() const
|
|
{
|
|
return toolbox_.width();
|
|
}
|
|
|
|
int _m_itembar_right() const
|
|
{
|
|
int right = _m_toolbox_pos();
|
|
if(toolbox_.renderable(toolbox_.ButtonAdd))
|
|
right -= static_cast<int>(toolbox_.item_pixels());
|
|
|
|
int end = static_cast<int>(list_.size() * basis_.item_pixels);
|
|
return (end < right ? end : right);
|
|
}
|
|
|
|
nana::rectangle _m_close_fly_area(int x)
|
|
{
|
|
return nana::rectangle(x + basis_.item_pixels - 18, (basis_.graph->height() - 14) / 2, 14, 14);
|
|
}
|
|
|
|
bool _m_item_pos(std::size_t index, int &left, int &right) const
|
|
{
|
|
if(index < list_.size())
|
|
{
|
|
left = static_cast<int>(index * basis_.item_pixels);
|
|
left -= static_cast<int>(basis_.scroll_pixels);
|
|
right = left + basis_.item_pixels;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::size_t _m_where_itembar(int x, int y, int end)
|
|
{
|
|
if(x < 0 || x >= end) return npos;
|
|
|
|
int left = -static_cast<int>(basis_.scroll_pixels);
|
|
std::size_t i = 0, size = list_.size();
|
|
|
|
for(; i != size; ++i)
|
|
{
|
|
if(left >= end)
|
|
{
|
|
i = npos;
|
|
break;
|
|
}
|
|
|
|
if(left <= x && x < left + static_cast<int>(basis_.item_pixels))
|
|
break;
|
|
|
|
left += basis_.item_pixels;
|
|
}
|
|
|
|
if(i < list_.size())
|
|
{
|
|
trace_.item_part = trace_.body;
|
|
if(toolbox_.close_fly())
|
|
{
|
|
nana::rectangle r = _m_close_fly_area(left);
|
|
if((r.x <= x && x < r.x + static_cast<int>(r.width)) && (r.y <= y && y < r.y + static_cast<int>(r.height)))
|
|
trace_.item_part = trace_.close;
|
|
}
|
|
return i;
|
|
}
|
|
return npos;
|
|
}
|
|
|
|
nana::rectangle _m_toolbox_area(toolbox::button_t btn) const
|
|
{
|
|
nana::rectangle r(0, 0, toolbox_.item_pixels(), basis_.graph->height());
|
|
if(btn == toolbox_.ButtonAdd)
|
|
{
|
|
int end = _m_itembar_right();
|
|
if(static_cast<int>(list_.size() * basis_.item_pixels) < end)
|
|
r.x = static_cast<int>(list_.size() * basis_.item_pixels);
|
|
else
|
|
r.x = end;
|
|
}
|
|
else if(toolbox_.ButtonClose == btn && toolbox_.close_fly())
|
|
{
|
|
r.x = -1;
|
|
r.width = r.height = 0;
|
|
}
|
|
else
|
|
r = toolbox_.area(btn, basis_.graph->height());
|
|
return r;
|
|
}
|
|
|
|
void _m_adjust()
|
|
{
|
|
if((nullptr == basis_.graph) || (0 == list_.size())) return;
|
|
|
|
//adjust the number of pixels of item.
|
|
bool scrollable = toolbox_.renderable(toolbox_.ButtonScrollBack);
|
|
if(scrollable)
|
|
{
|
|
toolbox_.visible(toolbox_.ButtonScrollBack, false);
|
|
toolbox_.visible(toolbox_.ButtonScrollNext, false);
|
|
}
|
|
unsigned beside = _m_toolbox_pixels();
|
|
if(toolbox_.renderable(toolbox_.ButtonAdd))
|
|
beside += toolbox_.item_pixels();
|
|
|
|
unsigned pixels = basis_.graph->width();
|
|
if(pixels <= beside)
|
|
return;
|
|
unsigned each_pixels = static_cast<unsigned>((pixels - beside) / list_.size());
|
|
if(each_pixels > basis_.max_pixels)
|
|
each_pixels = basis_.max_pixels;
|
|
else if(each_pixels < basis_.min_pixels)
|
|
each_pixels = basis_.min_pixels;
|
|
|
|
unsigned total = static_cast<unsigned>(each_pixels * list_.size());
|
|
if(total > pixels - beside && toolbox_.enable(toolbox_.ButtonScrollBack))
|
|
{
|
|
toolbox_.visible(toolbox_.ButtonScrollBack, true);
|
|
toolbox_.visible(toolbox_.ButtonScrollNext, true);
|
|
|
|
beside = _m_toolbox_pixels();
|
|
if(toolbox_.renderable(toolbox_.ButtonAdd))
|
|
beside += toolbox_.item_pixels();
|
|
|
|
if(pixels <= beside)
|
|
return;
|
|
|
|
each_pixels = static_cast<unsigned>((pixels - beside) / list_.size());
|
|
if(each_pixels > basis_.max_pixels)
|
|
each_pixels = basis_.max_pixels;
|
|
else if(each_pixels < basis_.min_pixels)
|
|
each_pixels = basis_.min_pixels;
|
|
}
|
|
else
|
|
basis_.scroll_pixels = 0;
|
|
|
|
if(each_pixels != basis_.item_pixels)
|
|
basis_.item_pixels = each_pixels;
|
|
|
|
if(scrollable != toolbox_.renderable(toolbox_.ButtonScrollBack))
|
|
basis_.scroll_pixels = static_cast<unsigned>(list_.size() * basis_.item_pixels) - _m_itembar_right();
|
|
}
|
|
|
|
item_renderer::state_t _m_state(unsigned index) const
|
|
{
|
|
return ((trace_.what == trace_.item) && (trace_.u.index == index) ? item_renderer::highlight : item_renderer::normal);
|
|
}
|
|
|
|
item_renderer::state_t _m_state(toolbox::button_t kind) const
|
|
{
|
|
return ((trace_.what == trace_.toolbox && trace_.u.button == kind) ? item_renderer::highlight : item_renderer::normal);
|
|
}
|
|
|
|
void _m_render()
|
|
{
|
|
if(!basis_.renderer || (nullptr == basis_.graph))
|
|
return;
|
|
|
|
auto bgcolor = API::bgcolor(basis_.wd);
|
|
auto fgcolor = API::fgcolor(basis_.wd);
|
|
|
|
item_renderer::item_t m;
|
|
m.r = ::nana::rectangle{ basis_.graph->size() };
|
|
|
|
basis_.renderer->background(*basis_.graph, m.r, bgcolor);
|
|
|
|
//the max number of pixels of tabs.
|
|
int pixels = static_cast<int>(m.r.width - _m_toolbox_pixels());
|
|
|
|
m.r.x = -static_cast<int>(basis_.scroll_pixels);
|
|
|
|
m.r.width = basis_.item_pixels;
|
|
|
|
unsigned index = 0;
|
|
bool is_close_fly = toolbox_.visible(toolbox_.ButtonClose) && toolbox_.enable(toolbox_.ButtonClose) && toolbox_.close_fly();
|
|
|
|
item_renderer::item_t active_m;
|
|
|
|
for(auto i = list_.cbegin(); i != list_.cend(); ++i, ++index)
|
|
{
|
|
if(m.r.x >= pixels) break;
|
|
|
|
if(m.r.x + static_cast<int>(basis_.item_pixels) > 0)
|
|
{
|
|
m.bgcolor = i->bgcolor;
|
|
m.fgcolor = i->fgcolor;
|
|
if(index == this->basis_.active)
|
|
active_m = m;
|
|
|
|
const item_t & item = *i;
|
|
basis_.renderer->item(*basis_.graph, m, (index == basis_.active), _m_state(index));
|
|
if(is_close_fly)
|
|
{
|
|
item_renderer::state_t sta = item_renderer::normal;
|
|
if(trace_.what == trace_.item && trace_.item_part == trace_.close && index == trace_.u.index)
|
|
sta = item_renderer::highlight;
|
|
basis_.renderer->close_fly(*basis_.graph, _m_close_fly_area(m.r.x), (index == basis_.active), sta);
|
|
}
|
|
|
|
if(false == item.img.empty())
|
|
item.img.stretch(::nana::rectangle{ item.img.size() }, *basis_.graph, nana::rectangle(m.r.x + 4, (m.r.height - 16) / 2, 16, 16));
|
|
|
|
if(item.text.size())
|
|
{
|
|
nana::size ts = basis_.graph->text_extent_size(item.text);
|
|
basis_.graph->palette(true, m.fgcolor.invisible() ? fgcolor : m.fgcolor);
|
|
nana::paint::text_renderer tr(*basis_.graph);
|
|
|
|
std::wstring wtext = to_wstring(item.text);
|
|
tr.render({ m.r.x + 24, m.r.y + static_cast<int>(m.r.height - ts.height) / 2 },
|
|
wtext.c_str(), wtext.length(), basis_.item_pixels - 24 - 18, true);
|
|
}
|
|
}
|
|
|
|
m.r.x += static_cast<int>(basis_.item_pixels);
|
|
}
|
|
|
|
_m_render_toolbox(bgcolor);
|
|
|
|
int bottom = static_cast<int>(basis_.graph->height()) - 1;
|
|
|
|
if(_m_nextable())
|
|
{
|
|
int x = _m_itembar_right();
|
|
if (x > 0)
|
|
{
|
|
basis_.graph->line({ x - 2, 0 }, { x - 2, bottom }, static_cast<color_rgb>(0x808080));
|
|
basis_.graph->line({ x - 1, 0 }, { x - 1, bottom }, static_cast<color_rgb>(0xf0f0f0));
|
|
}
|
|
}
|
|
|
|
basis_.graph->palette(false, static_cast<color_rgb>(0x808080));
|
|
|
|
int right = static_cast<int>(basis_.graph->width());
|
|
int end = active_m.r.x + static_cast<int>(active_m.r.width);
|
|
if(0 < active_m.r.x && active_m.r.x < right)
|
|
basis_.graph->line({ 0, bottom }, { active_m.r.x, bottom });
|
|
if(0 <= end && end < right)
|
|
basis_.graph->line({ end, bottom }, { right, bottom });
|
|
}
|
|
|
|
void _m_render_toolbox(const ::nana::color& bgcolor)
|
|
{
|
|
bool backable = (basis_.scroll_pixels != 0);
|
|
int xbase = _m_toolbox_pos();
|
|
basis_.graph->rectangle({ xbase, 0, _m_toolbox_pixels(), basis_.graph->height() }, true, bgcolor);
|
|
for(int i = toolbox::ButtonAdd; i < toolbox::ButtonSize; ++i)
|
|
{
|
|
toolbox::button_t btn = static_cast<toolbox::button_t>(i);
|
|
|
|
if(toolbox_.renderable(btn) == false) continue;
|
|
nana::rectangle r = _m_toolbox_area(btn);
|
|
if(toolbox_.ButtonAdd != btn)
|
|
r.x += xbase;
|
|
|
|
item_renderer::state_t state = item_renderer::normal;
|
|
if((trace_.what == trace_.toolbox) && (trace_.u.button == btn))
|
|
state = trace_.state;
|
|
|
|
switch(btn)
|
|
{
|
|
case toolbox::ButtonScrollBack:
|
|
basis_.renderer->back(*basis_.graph, r, (backable ? state : item_renderer::disable));
|
|
break;
|
|
case toolbox::ButtonScrollNext:
|
|
basis_.renderer->next(*basis_.graph, r, (_m_nextable() ? state : item_renderer::disable));
|
|
break;
|
|
case toolbox::ButtonList:
|
|
basis_.renderer->list(*basis_.graph, r, state);
|
|
break;
|
|
case toolbox::ButtonClose:
|
|
basis_.renderer->close(*basis_.graph, r, state);
|
|
break;
|
|
case toolbox::ButtonAdd:
|
|
basis_.renderer->add(*basis_.graph, r, state);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
private:
|
|
std::list<item_t> list_;
|
|
event_agent_interface* evt_agent_ = nullptr;
|
|
toolbox toolbox_;
|
|
::nana::menu menulister_;
|
|
|
|
struct trace_tag
|
|
{
|
|
enum t{null, item, toolbox};
|
|
enum item_t{body, close};
|
|
t what;
|
|
item_t item_part; //it is valid while "what" is item.
|
|
item_renderer::state_t state;
|
|
union
|
|
{
|
|
std::size_t index;
|
|
toolbox::button_t button;
|
|
}u;
|
|
|
|
trace_tag():what(null), state(item_renderer::normal)
|
|
{}
|
|
|
|
}trace_;
|
|
|
|
struct basis_tag
|
|
{
|
|
window wd{nullptr};
|
|
nana::paint::graphics * graph{nullptr};
|
|
pat::cloneable<item_renderer> renderer;
|
|
unsigned max_pixels{250};
|
|
unsigned min_pixels{100};
|
|
unsigned item_pixels{max_pixels};
|
|
unsigned scroll_pixels{0};
|
|
std::size_t active{npos};
|
|
|
|
basis_tag():renderer{ def_renderer() }
|
|
{}
|
|
}basis_;
|
|
};
|
|
|
|
//class trigger
|
|
trigger::trigger()
|
|
: layouter_(new layouter)
|
|
{}
|
|
|
|
trigger::~trigger()
|
|
{
|
|
delete layouter_;
|
|
}
|
|
|
|
void trigger::activate(std::size_t pos)
|
|
{
|
|
if(layouter_->activate(pos))
|
|
API::refresh_window(layouter_->widget_handle());
|
|
}
|
|
|
|
std::size_t trigger::activated() const
|
|
{
|
|
return layouter_->active();
|
|
}
|
|
|
|
nana::any& trigger::at(std::size_t i) const
|
|
{
|
|
return layouter_->at(i);
|
|
}
|
|
|
|
nana::any& trigger::at_no_bound_check(std::size_t i) const
|
|
{
|
|
return layouter_->at_no_bound_check(i);
|
|
}
|
|
|
|
const pat::cloneable<item_renderer> & trigger::ext_renderer() const
|
|
{
|
|
return layouter_->ext_renderer();
|
|
}
|
|
|
|
void trigger::ext_renderer(const pat::cloneable<item_renderer>& ir)
|
|
{
|
|
layouter_->ext_renderer(ir);
|
|
}
|
|
|
|
void trigger::set_event_agent(event_agent_interface* evt)
|
|
{
|
|
layouter_->event_agent(evt);
|
|
}
|
|
|
|
void trigger::insert(std::size_t pos, native_string_type&& text, nana::any&& value)
|
|
{
|
|
layouter_->insert(pos, std::move(text), std::move(value));
|
|
}
|
|
|
|
std::size_t trigger::length() const
|
|
{
|
|
return layouter_->length();
|
|
}
|
|
|
|
bool trigger::close_fly(bool fly)
|
|
{
|
|
return layouter_->toolbox_object().close_fly(fly);
|
|
}
|
|
|
|
window trigger::attach(std::size_t pos, window wd, bool drop_other)
|
|
{
|
|
return layouter_->attach(pos, wd, drop_other);
|
|
}
|
|
|
|
void trigger::erase(std::size_t pos)
|
|
{
|
|
layouter_->erase(pos);
|
|
}
|
|
|
|
void trigger::tab_color(std::size_t i, bool is_bgcolor, const ::nana::color& clr)
|
|
{
|
|
if(layouter_->tab_color(i, is_bgcolor, clr))
|
|
API::refresh_window(layouter_->widget_handle());
|
|
}
|
|
|
|
void trigger::tab_image(std::size_t i, const nana::paint::image& img)
|
|
{
|
|
layouter_->tab_image(i, img);
|
|
API::refresh_window(layouter_->widget_handle());
|
|
}
|
|
|
|
void trigger::text(std::size_t i, const native_string_type& str)
|
|
{
|
|
if(layouter_->text(i, str))
|
|
API::refresh_window(layouter_->widget_handle());
|
|
}
|
|
|
|
native_string_type trigger::text(std::size_t i) const
|
|
{
|
|
return layouter_->text(i);
|
|
}
|
|
|
|
bool trigger::toolbox(kits btn, bool enable)
|
|
{
|
|
auto tb = toolbox::ButtonSize;
|
|
auto& tbox = layouter_->toolbox_object();
|
|
switch(btn)
|
|
{
|
|
case kits::add:
|
|
tb = toolbox::ButtonAdd; break;
|
|
case kits::list:
|
|
tb = toolbox::ButtonList; break;
|
|
case kits::close:
|
|
tb = toolbox::ButtonClose; break;
|
|
case kits::scroll:
|
|
tbox.enable(toolbox::ButtonScrollBack, enable);
|
|
return tbox.enable(tbox.ButtonScrollNext, enable);
|
|
}
|
|
return (tb != toolbox::ButtonSize ? tbox.enable(tb, enable) : false);
|
|
}
|
|
|
|
void trigger::attached(widget_reference widget, graph_reference graph)
|
|
{
|
|
layouter_->attach(widget, graph);
|
|
}
|
|
|
|
void trigger::detached()
|
|
{
|
|
layouter_->detach();
|
|
}
|
|
|
|
void trigger::refresh(graph_reference)
|
|
{
|
|
layouter_->render();
|
|
}
|
|
|
|
void trigger::mouse_down(graph_reference, const arg_mouse& arg)
|
|
{
|
|
if(layouter_->press())
|
|
{
|
|
if(false == layouter_->active_by_trace())
|
|
layouter_->toolbox_answer(arg);
|
|
layouter_->render();
|
|
API::dev::lazy_refresh();
|
|
}
|
|
}
|
|
|
|
void trigger::mouse_up(graph_reference, const arg_mouse& arg)
|
|
{
|
|
bool rd = layouter_->release();
|
|
rd |= layouter_->toolbox_answer(arg);
|
|
if(rd)
|
|
{
|
|
layouter_->render();
|
|
API::dev::lazy_refresh();
|
|
}
|
|
}
|
|
|
|
void trigger::mouse_move(graph_reference, const arg_mouse& arg)
|
|
{
|
|
if(layouter_->trace(arg.pos.x, arg.pos.y))
|
|
{
|
|
layouter_->render();
|
|
API::dev::lazy_refresh();
|
|
}
|
|
}
|
|
|
|
void trigger::mouse_leave(graph_reference, const arg_mouse&)
|
|
{
|
|
if(layouter_->leave())
|
|
{
|
|
layouter_->render();
|
|
API::dev::lazy_refresh();
|
|
}
|
|
}
|
|
//end class trigger
|
|
}//end namespace tabbar
|
|
}//end namespace drawerbase
|
|
}//end namespace nana
|
|
|
|
#include <forward_list>
|
|
namespace nana
|
|
{
|
|
namespace drawerbase
|
|
{
|
|
namespace tabbar_lite
|
|
{
|
|
struct item
|
|
{
|
|
::std::string text;
|
|
::nana::any value;
|
|
::std::pair<int, int> pos_ends;
|
|
::nana::window attached_window{ nullptr };
|
|
|
|
item(std::string t, ::nana::any v)
|
|
: text(std::move(t)), value(std::move(v))
|
|
{}
|
|
};
|
|
|
|
void calc_px(std::vector<unsigned>& values, unsigned limit)
|
|
{
|
|
unsigned base = 0;
|
|
auto count = values.size();
|
|
|
|
while (count)
|
|
{
|
|
unsigned minv = static_cast<unsigned>(-1);
|
|
|
|
unsigned minv_count = 0;
|
|
for (auto u : values)
|
|
{
|
|
if (u >= base && u < minv)
|
|
{
|
|
minv_count = 1;
|
|
minv = u;
|
|
}
|
|
else if (minv == u)
|
|
minv_count++;
|
|
}
|
|
|
|
count -= minv_count;
|
|
if (minv * count >= limit)
|
|
{
|
|
auto piece = limit / double(count);
|
|
double deviation = 0;
|
|
for (auto & u : values)
|
|
{
|
|
if (minv >= u)
|
|
{
|
|
auto px = piece + deviation;
|
|
u = static_cast<unsigned>(px);
|
|
deviation = px - u;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
base = minv;
|
|
limit -= minv_count * minv;
|
|
}
|
|
}
|
|
|
|
class model
|
|
{
|
|
struct indexes
|
|
{
|
|
std::size_t hovered_pos{ npos };
|
|
std::size_t active_pos{ 0 };
|
|
};
|
|
public:
|
|
using graph_reference = ::nana::paint::graphics&;
|
|
static const std::size_t npos = static_cast<std::size_t>(-1);
|
|
|
|
void set_widget(::nana::tabbar_lite& wdg)
|
|
{
|
|
widget_ = &wdg;
|
|
}
|
|
|
|
::nana::tabbar_lite* widget_ptr() const
|
|
{
|
|
return widget_;
|
|
}
|
|
|
|
std::forward_list<item>& items()
|
|
{
|
|
return items_;
|
|
}
|
|
|
|
void show_attached_window()
|
|
{
|
|
if (indexes_.active_pos != npos)
|
|
{
|
|
auto i = items_.cbegin();
|
|
std::advance(i, indexes_.active_pos);
|
|
API::show_window(i->attached_window, true);
|
|
|
|
std::size_t pos = 0;
|
|
for (auto & m : items_)
|
|
{
|
|
if (pos++ != indexes_.active_pos)
|
|
API::show_window(m.attached_window, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool track_pointer(const point& pos)
|
|
{
|
|
std::size_t item_pos = 0;
|
|
bool matched = false;
|
|
for (auto & m : items_)
|
|
{
|
|
if (m.pos_ends.first <= pos.x && pos.x < m.pos_ends.second)
|
|
{
|
|
matched = true;
|
|
break;
|
|
}
|
|
|
|
++item_pos;
|
|
}
|
|
|
|
if (!matched)
|
|
item_pos = npos;
|
|
|
|
if (indexes_.hovered_pos == item_pos)
|
|
return false;
|
|
|
|
indexes_.hovered_pos = item_pos;
|
|
return true;
|
|
}
|
|
|
|
indexes& get_indexes()
|
|
{
|
|
return indexes_;
|
|
}
|
|
private:
|
|
::nana::tabbar_lite * widget_{ nullptr };
|
|
std::forward_list<item> items_;
|
|
indexes indexes_;
|
|
};
|
|
|
|
class renderer
|
|
{
|
|
public:
|
|
using graph_reference = ::nana::paint::graphics&;
|
|
|
|
void render(graph_reference graph, model& model)
|
|
{
|
|
_m_calc_metrics(graph, model.items());
|
|
|
|
auto & scheme = model.widget_ptr()->scheme();
|
|
|
|
//draw background
|
|
graph.rectangle(true, scheme.background);
|
|
|
|
auto & indexes = model.get_indexes();
|
|
std::size_t pos = 0;
|
|
for (auto & m : model.items())
|
|
{
|
|
rectangle r{ m.pos_ends.first, 0, static_cast<unsigned>(m.pos_ends.second - m.pos_ends.first), graph.height() };
|
|
if (indexes.active_pos == pos)
|
|
{
|
|
graph.palette(false, colors::white);
|
|
graph.palette(true, colors::black);
|
|
}
|
|
else
|
|
{
|
|
::nana::color bgcolor(colors::coral);
|
|
|
|
if (pos == indexes.hovered_pos)
|
|
bgcolor = bgcolor.blend(colors::white, 0.5);
|
|
|
|
graph.palette(false, bgcolor);
|
|
graph.palette(true, colors::white);
|
|
}
|
|
|
|
graph.rectangle(r, true);
|
|
graph.bidi_string({ m.pos_ends.first + 5, 0 }, m.text.data(), m.text.size());
|
|
|
|
++pos;
|
|
}
|
|
|
|
}
|
|
private:
|
|
void _m_calc_metrics(graph_reference graph, std::forward_list<item>& items)
|
|
{
|
|
std::vector<unsigned> pxs;
|
|
|
|
unsigned pixels = 0;
|
|
for (auto & m : items)
|
|
{
|
|
auto ts = graph.bidi_extent_size(m.text);
|
|
pxs.push_back(ts.width + 12);
|
|
pixels += ts.width + 12;
|
|
}
|
|
|
|
if (pixels > graph.width())
|
|
calc_px(pxs, graph.width());
|
|
|
|
auto i = pxs.cbegin();
|
|
int pos = 0;
|
|
for (auto & m : items)
|
|
{
|
|
m.pos_ends.first = pos;
|
|
m.pos_ends.second = (pos += static_cast<int>(*i++));
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
//class driver
|
|
driver::driver()
|
|
: model_(new model)
|
|
{
|
|
}
|
|
|
|
driver::~driver()
|
|
{
|
|
delete model_;
|
|
}
|
|
|
|
model* driver::get_model() const throw()
|
|
{
|
|
return model_;
|
|
}
|
|
|
|
void driver::attached(widget_reference wdg, graph_reference)
|
|
{
|
|
model_->set_widget(dynamic_cast<nana::tabbar_lite&>(wdg));
|
|
}
|
|
|
|
//Overrides drawer_trigger's method
|
|
void driver::refresh(graph_reference graph)
|
|
{
|
|
renderer rd;
|
|
rd.render(graph, *model_);
|
|
}
|
|
|
|
void driver::mouse_move(graph_reference graph, const arg_mouse& arg)
|
|
{
|
|
if (!model_->track_pointer(arg.pos))
|
|
return;
|
|
|
|
refresh(graph);
|
|
API::dev::lazy_refresh();
|
|
}
|
|
|
|
void driver::mouse_leave(graph_reference graph, const arg_mouse&)
|
|
{
|
|
if (model_->get_indexes().hovered_pos == model_->npos)
|
|
return;
|
|
|
|
refresh(graph);
|
|
API::dev::lazy_refresh();
|
|
}
|
|
|
|
void driver::mouse_down(graph_reference graph, const arg_mouse&)
|
|
{
|
|
auto & indexes = model_->get_indexes();
|
|
if ((indexes.hovered_pos == model_->npos) || (indexes.active_pos == indexes.hovered_pos))
|
|
return;
|
|
|
|
if (indexes.active_pos != indexes.hovered_pos)
|
|
{
|
|
indexes.active_pos = indexes.hovered_pos;
|
|
model_->show_attached_window();
|
|
|
|
refresh(graph);
|
|
API::dev::lazy_refresh();
|
|
|
|
event_arg arg;
|
|
model_->widget_ptr()->events().selected.emit(arg, model_->widget_ptr()->handle());
|
|
}
|
|
}
|
|
//end class driver
|
|
}
|
|
}
|
|
|
|
//class tabbar
|
|
|
|
tabbar_lite::tabbar_lite(window parent_wd, bool visible, const ::nana::rectangle& r)
|
|
{
|
|
this->create(parent_wd, r, visible);
|
|
}
|
|
|
|
//capacity
|
|
std::size_t tabbar_lite::length() const
|
|
{
|
|
auto& items = get_drawer_trigger().get_model()->items();
|
|
internal_scope_guard lock;
|
|
return static_cast<std::size_t>(std::distance(items.cbegin(), items.cend()));
|
|
}
|
|
|
|
//modifiers
|
|
void tabbar_lite::attach(std::size_t pos_set, window wd)
|
|
{
|
|
auto model = get_drawer_trigger().get_model();
|
|
internal_scope_guard lock;
|
|
|
|
for (auto & m : model->items())
|
|
{
|
|
if (0 == pos_set--)
|
|
{
|
|
m.attached_window = wd;
|
|
model->show_attached_window();
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw std::out_of_range("invalid position of tabbar_lite");
|
|
}
|
|
|
|
window tabbar_lite::attach(std::size_t pos_set) const
|
|
{
|
|
auto model = get_drawer_trigger().get_model();
|
|
internal_scope_guard lock;
|
|
|
|
for (auto & m : model->items())
|
|
{
|
|
if (0 == pos_set--)
|
|
return m.attached_window;
|
|
}
|
|
|
|
throw std::out_of_range("invalid position of tabbar_lite");
|
|
}
|
|
|
|
void tabbar_lite::push_back(std::string text, ::nana::any any)
|
|
{
|
|
auto & items = get_drawer_trigger().get_model()->items();
|
|
internal_scope_guard lock;
|
|
|
|
auto i = items.cbefore_begin();
|
|
while (true)
|
|
{
|
|
auto next = i++;
|
|
if (i == items.cend())
|
|
{
|
|
items.emplace_after(next, std::move(text), std::move(any));
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
API::refresh_window(handle());
|
|
}
|
|
|
|
void tabbar_lite::push_front(std::string text, ::nana::any any)
|
|
{
|
|
auto & items = get_drawer_trigger().get_model()->items();
|
|
internal_scope_guard lock;
|
|
|
|
items.emplace_front(std::move(text), std::move(any));
|
|
API::refresh_window(handle());
|
|
}
|
|
|
|
std::size_t tabbar_lite::selected() const
|
|
{
|
|
auto model = get_drawer_trigger().get_model();
|
|
internal_scope_guard lock;
|
|
|
|
return model->get_indexes().active_pos;
|
|
}
|
|
|
|
void tabbar_lite::erase(std::size_t pos, bool close_attached)
|
|
{
|
|
auto model = get_drawer_trigger().get_model();
|
|
internal_scope_guard lock;
|
|
|
|
const auto len = length();
|
|
|
|
if (len <= pos)
|
|
throw std::out_of_range("invalid position of tabbar_lite");
|
|
|
|
auto active_pos = model->get_indexes().active_pos;
|
|
|
|
//selection_changed is used to determine whether the title will be updated
|
|
bool selection_changed = true;
|
|
if (pos == active_pos)
|
|
{
|
|
if (active_pos + 1 == len)
|
|
{
|
|
if (active_pos)
|
|
--active_pos;
|
|
else
|
|
active_pos = npos;
|
|
}
|
|
}
|
|
else if (pos < active_pos)
|
|
--active_pos;
|
|
else
|
|
selection_changed = false;
|
|
|
|
model->get_indexes().active_pos = active_pos;
|
|
|
|
auto i = model->items().cbefore_begin();
|
|
std::advance(i, pos);
|
|
|
|
auto attached_wd = std::next(i)->attached_window;
|
|
|
|
model->items().erase_after(i);
|
|
|
|
model->show_attached_window();
|
|
API::refresh_window(handle());
|
|
|
|
if (close_attached && attached_wd)
|
|
API::close_window(attached_wd);
|
|
|
|
if (selection_changed && (active_pos != npos))
|
|
{
|
|
event_arg arg;
|
|
events().selected.emit(arg, handle());
|
|
}
|
|
}
|
|
//end class tabbar
|
|
}//end namespace nana
|