511 lines
12 KiB
C++
511 lines
12 KiB
C++
/*
|
|
* A float_listbox Implementation
|
|
* Nana C++ Library(http://www.nanapro.org)
|
|
* 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/float_listbox.cpp
|
|
*/
|
|
|
|
#include <nana/gui/widgets/float_listbox.hpp>
|
|
#include <nana/gui/widgets/scroll.hpp>
|
|
|
|
namespace nana
|
|
{
|
|
namespace drawerbase{
|
|
namespace float_listbox
|
|
{
|
|
class def_item_renderer
|
|
: public item_renderer
|
|
{
|
|
bool image_enabled_;
|
|
unsigned image_pixels_;
|
|
|
|
void image(bool enb, unsigned px)
|
|
{
|
|
image_enabled_ = enb;
|
|
image_pixels_ = px;
|
|
}
|
|
|
|
void render(widget_reference, graph_reference graph, const nana::rectangle& r, const item_interface* item, state_t state)
|
|
{
|
|
if (state == StateHighlighted)
|
|
{
|
|
::nana::color clr(static_cast<color_rgb>(0xafc7e3));
|
|
graph.rectangle(r, false, clr);
|
|
|
|
auto right = r.right() - 1;
|
|
auto bottom = r.bottom() - 1;
|
|
graph.palette(false, colors::white);
|
|
graph.set_pixel(r.x, r.y);
|
|
graph.set_pixel(right, r.y);
|
|
graph.set_pixel(r.x, bottom);
|
|
graph.set_pixel(right, bottom);
|
|
|
|
--right;
|
|
--bottom;
|
|
graph.palette(false, clr);
|
|
graph.set_pixel(r.x + 1, r.y + 1);
|
|
graph.set_pixel(right, r.y + 1);
|
|
graph.set_pixel(r.x + 1, bottom);
|
|
graph.set_pixel(right, bottom);
|
|
|
|
nana::rectangle po_r(r);
|
|
graph.rectangle(po_r.pare_off(1), false, static_cast<color_rgb>(0xEBF4FB));
|
|
graph.gradual_rectangle(po_r.pare_off(1), static_cast<color_rgb>(0xDDECFD), static_cast<color_rgb>(0xC2DCFD), true);
|
|
}
|
|
else
|
|
graph.rectangle(r, true, colors::white);
|
|
|
|
int x = r.x + 2;
|
|
if(image_enabled_)
|
|
{
|
|
unsigned vpix = (r.height - 4);
|
|
if(item->image())
|
|
{
|
|
nana::size imgsz = item->image().size();
|
|
if(imgsz.width > image_pixels_)
|
|
{
|
|
unsigned new_h = image_pixels_ * imgsz.height / imgsz.width;
|
|
if(new_h > vpix)
|
|
{
|
|
imgsz.width = vpix * imgsz.width / imgsz.height;
|
|
imgsz.height = vpix;
|
|
}
|
|
else
|
|
{
|
|
imgsz.width = image_pixels_;
|
|
imgsz.height = new_h;
|
|
}
|
|
}
|
|
else if(imgsz.height > vpix)
|
|
{
|
|
unsigned new_w = vpix * imgsz.width / imgsz.height;
|
|
if(new_w > image_pixels_)
|
|
{
|
|
imgsz.height = image_pixels_ * imgsz.height / imgsz.width;
|
|
imgsz.width = image_pixels_;
|
|
}
|
|
else
|
|
{
|
|
imgsz.height = vpix;
|
|
imgsz.width = new_w;
|
|
}
|
|
}
|
|
|
|
nana::point to_pos(x, r.y + 2);
|
|
to_pos.x += (image_pixels_ - imgsz.width) / 2;
|
|
to_pos.y += (vpix - imgsz.height) / 2;
|
|
item->image().stretch(::nana::rectangle{ item->image().size() }, graph, nana::rectangle(to_pos, imgsz));
|
|
}
|
|
x += (image_pixels_ + 2);
|
|
}
|
|
|
|
graph.string({ x, r.y + 2 }, item->text(), colors::black);
|
|
}
|
|
|
|
unsigned item_pixels(graph_reference graph) const
|
|
{
|
|
return graph.text_extent_size(L"jHWn/?\\{[(0569").height + 4;
|
|
}
|
|
};//end class item_renderer
|
|
|
|
//class drawer_impl
|
|
class drawer_impl
|
|
{
|
|
public:
|
|
using widget_reference = widget&;
|
|
using graph_reference = paint::graphics&;
|
|
|
|
void clear_state()
|
|
{
|
|
state_.offset_y = 0;
|
|
state_.index = npos;
|
|
}
|
|
|
|
void ignore_first_mouse_up(bool value)
|
|
{
|
|
ignore_first_mouseup_ = value;
|
|
}
|
|
|
|
bool ignore_emitting_mouseup()
|
|
{
|
|
if(ignore_first_mouseup_)
|
|
{
|
|
ignore_first_mouseup_ = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void renderer(item_renderer* ir)
|
|
{
|
|
state_.renderer = (ir ? ir : state_.orig_renderer);
|
|
}
|
|
|
|
void scroll_items(bool upwards)
|
|
{
|
|
if(scrollbar_.empty()) return;
|
|
|
|
const auto before_change = state_.offset_y;
|
|
if(upwards)
|
|
{
|
|
if (before_change)
|
|
--(state_.offset_y);
|
|
}
|
|
else
|
|
{
|
|
if ((before_change + module_->max_items) < module_->items.size())
|
|
++(state_.offset_y);
|
|
}
|
|
|
|
if(before_change != state_.offset_y)
|
|
{
|
|
draw();
|
|
scrollbar_.value(state_.offset_y);
|
|
API::update_window(*widget_);
|
|
}
|
|
}
|
|
|
|
void move_items(bool upwards, bool recycle)
|
|
{
|
|
if(module_ && module_->items.size())
|
|
{
|
|
std::size_t init_index = state_.index;
|
|
if(state_.index != npos)
|
|
{
|
|
unsigned last_offset_y = 0;
|
|
if(module_->items.size() > module_->max_items)
|
|
last_offset_y = static_cast<unsigned>(module_->items.size() - module_->max_items);
|
|
|
|
if(upwards)
|
|
{
|
|
if(state_.index)
|
|
--(state_.index);
|
|
else if(recycle)
|
|
{
|
|
state_.index = static_cast<unsigned>(module_->items.size() - 1);
|
|
state_.offset_y = last_offset_y;
|
|
}
|
|
|
|
if(state_.index < state_.offset_y)
|
|
state_.offset_y = state_.index;
|
|
}
|
|
else
|
|
{
|
|
if(state_.index < module_->items.size() - 1)
|
|
++(state_.index);
|
|
else if(recycle)
|
|
{
|
|
state_.index = 0;
|
|
state_.offset_y = 0;
|
|
}
|
|
|
|
if(state_.index >= state_.offset_y + module_->max_items)
|
|
state_.offset_y = static_cast<unsigned>(state_.index - module_->max_items + 1);
|
|
}
|
|
}
|
|
else
|
|
state_.index = 0;
|
|
|
|
if(init_index != state_.index)
|
|
{
|
|
draw();
|
|
scrollbar_.value(state_.offset_y);
|
|
API::update_window(*widget_);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::size_t index() const
|
|
{
|
|
return state_.index;
|
|
}
|
|
|
|
widget* widget_ptr()
|
|
{
|
|
return widget_;
|
|
}
|
|
|
|
void attach(widget* wd, nana::paint::graphics* graph)
|
|
{
|
|
if(wd)
|
|
{
|
|
widget_ = wd;
|
|
wd->events().mouse_wheel.connect_unignorable([this](const arg_wheel& arg){
|
|
scroll_items(arg.upwards);
|
|
});
|
|
}
|
|
if(graph) graph_ = graph;
|
|
}
|
|
|
|
void detach()
|
|
{
|
|
graph_ = nullptr;
|
|
}
|
|
|
|
void resize()
|
|
{
|
|
if(module_)
|
|
{
|
|
std::size_t items = (module_->max_items <= module_->items.size() ? module_->max_items : module_->items.size());
|
|
std::size_t h = items * state_.renderer->item_pixels(*graph_);
|
|
widget_->size(size{ widget_->size().width, static_cast<unsigned>(h + 4) });
|
|
}
|
|
}
|
|
|
|
void set_module(const module_def& md, unsigned pixels)
|
|
{
|
|
module_ = &md;
|
|
md.have_selected = false;
|
|
if(md.index >= md.items.size())
|
|
md.index = npos;
|
|
|
|
image_pixels_ = pixels;
|
|
}
|
|
|
|
void set_result()
|
|
{
|
|
if(module_)
|
|
{
|
|
module_->index = state_.index;
|
|
module_->have_selected = true;
|
|
}
|
|
}
|
|
|
|
static bool right_area(graph_reference graph, int x, int y)
|
|
{
|
|
return ((1 < x && 1 < y) &&
|
|
x < static_cast<int>(graph.width()) - 2 &&
|
|
y < static_cast<int>(graph.height()) - 2);
|
|
}
|
|
|
|
bool set_mouse(graph_reference graph, int x, int y)
|
|
{
|
|
if(this->right_area(graph, x, y))
|
|
{
|
|
const unsigned n = (y - 2) / state_.renderer->item_pixels(graph) + static_cast<unsigned>(state_.offset_y);
|
|
if(n != state_.index)
|
|
{
|
|
state_.index = n;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void draw()
|
|
{
|
|
if(module_)
|
|
{
|
|
bool pages = (module_->max_items < module_->items.size());
|
|
const unsigned outter_w = (pages ? 20 : 4);
|
|
|
|
if(graph_->width() > outter_w && graph_->height() > 4 )
|
|
{
|
|
//Draw items
|
|
std::size_t items = (pages ? module_->max_items : module_->items.size());
|
|
items += state_.offset_y;
|
|
|
|
const unsigned item_pixels = state_.renderer->item_pixels(*graph_);
|
|
nana::rectangle item_r(2, 2, graph_->width() - outter_w, item_pixels);
|
|
|
|
state_.renderer->image(_m_image_enabled(), image_pixels_);
|
|
for(std::size_t i = state_.offset_y; i < items; ++i)
|
|
{
|
|
auto state = (i != state_.index ? item_renderer::StateNone : item_renderer::StateHighlighted);
|
|
|
|
state_.renderer->render(*widget_, *graph_, item_r, module_->items[i].get(), state);
|
|
item_r.y += item_pixels;
|
|
}
|
|
}
|
|
_m_open_scrollbar(*widget_, pages);
|
|
}
|
|
else
|
|
graph_->string({ 4, 4 }, L"Empty Listbox, No Module!", static_cast<color_rgb>(0x808080));
|
|
|
|
//Draw border
|
|
graph_->rectangle(false, colors::black);
|
|
graph_->rectangle(nana::rectangle(graph_->size()).pare_off(1), false, colors::white);
|
|
}
|
|
private:
|
|
bool _m_image_enabled() const
|
|
{
|
|
for(auto & i : module_->items)
|
|
{
|
|
if(false == i->image().empty())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void _m_open_scrollbar(widget_reference wd, bool v)
|
|
{
|
|
if(v)
|
|
{
|
|
if(scrollbar_.empty() && module_)
|
|
{
|
|
scrollbar_.create(wd, rectangle(static_cast<int>(wd.size().width - 18), 2, 16, wd.size().height - 4));
|
|
scrollbar_.amount(module_->items.size());
|
|
scrollbar_.range(module_->max_items);
|
|
scrollbar_.value(state_.offset_y);
|
|
|
|
auto & events = scrollbar_.events();
|
|
events.mouse_wheel.connect([this](const arg_wheel& arg)
|
|
{
|
|
scroll_items(arg.upwards);
|
|
});
|
|
|
|
auto fn = [this](const arg_mouse& arg)
|
|
{
|
|
if (arg.is_left_button() && (scrollbar_.value() != state_.offset_y))
|
|
{
|
|
state_.offset_y = static_cast<unsigned>(scrollbar_.value());
|
|
draw();
|
|
API::update_window(*widget_);
|
|
}
|
|
};
|
|
events.mouse_move.connect(fn);
|
|
events.mouse_up.connect(fn);
|
|
}
|
|
}
|
|
else
|
|
scrollbar_.close();
|
|
}
|
|
private:
|
|
widget * widget_{nullptr};
|
|
nana::paint::graphics * graph_{nullptr};
|
|
unsigned image_pixels_{16}; //Define the width pixels of the image area
|
|
|
|
bool ignore_first_mouseup_{true};
|
|
struct state_type
|
|
{
|
|
std::size_t offset_y{0};
|
|
std::size_t index{npos}; //The index of the selected item.
|
|
|
|
item_renderer * const orig_renderer;
|
|
item_renderer * renderer;
|
|
|
|
state_type(): orig_renderer(new def_item_renderer), renderer(orig_renderer){}
|
|
~state_type()
|
|
{
|
|
delete orig_renderer;
|
|
}
|
|
}state_;
|
|
nana::scroll<true> scrollbar_;
|
|
|
|
const module_def* module_{nullptr};
|
|
};
|
|
|
|
//class drawer_impl;
|
|
|
|
|
|
//class trigger
|
|
trigger::trigger()
|
|
:drawer_(new drawer_impl)
|
|
{}
|
|
|
|
trigger::~trigger()
|
|
{
|
|
delete drawer_;
|
|
}
|
|
|
|
drawer_impl& trigger::get_drawer_impl()
|
|
{
|
|
return *drawer_;
|
|
}
|
|
|
|
const drawer_impl& trigger::get_drawer_impl() const
|
|
{
|
|
return *drawer_;
|
|
}
|
|
|
|
void trigger::attached(widget_reference widget, graph_reference graph)
|
|
{
|
|
drawer_->attach(&widget, &graph);
|
|
}
|
|
|
|
void trigger::detached()
|
|
{
|
|
drawer_->detach();
|
|
}
|
|
|
|
void trigger::refresh(graph_reference)
|
|
{
|
|
drawer_->draw();
|
|
}
|
|
|
|
void trigger::mouse_move(graph_reference graph, const arg_mouse& arg)
|
|
{
|
|
if(drawer_->set_mouse(graph, arg.pos.x, arg.pos.y))
|
|
{
|
|
drawer_->draw();
|
|
API::lazy_refresh();
|
|
}
|
|
}
|
|
|
|
void trigger::mouse_up(graph_reference graph, const arg_mouse& arg)
|
|
{
|
|
bool close_wdg = false;
|
|
if (drawer_->right_area(graph, arg.pos.x, arg.pos.y))
|
|
{
|
|
drawer_->set_result();
|
|
close_wdg = true;
|
|
}
|
|
else
|
|
close_wdg = (false == drawer_->ignore_emitting_mouseup());
|
|
|
|
if (close_wdg)
|
|
drawer_->widget_ptr()->close();
|
|
}
|
|
//end class trigger
|
|
}
|
|
}//end namespace drawerbase
|
|
|
|
//class float_listbox
|
|
float_listbox::float_listbox(window wd, const rectangle & r, bool is_ignore_first_mouse_up)
|
|
:base_type(wd, false, r, appear::bald<appear::floating, appear::no_activate>())
|
|
{
|
|
API::capture_window(handle(), true);
|
|
API::capture_ignore_children(false);
|
|
API::take_active(handle(), false, parent());
|
|
auto & impl = get_drawer_trigger().get_drawer_impl();
|
|
impl.clear_state();
|
|
impl.ignore_first_mouse_up(is_ignore_first_mouse_up);
|
|
}
|
|
|
|
void float_listbox::set_module(const float_listbox::module_type& md, unsigned pixels)
|
|
{
|
|
auto & impl = get_drawer_trigger().get_drawer_impl();
|
|
impl.set_module(md, pixels);
|
|
impl.resize();
|
|
show();
|
|
}
|
|
|
|
void float_listbox::scroll_items(bool upwards)
|
|
{
|
|
get_drawer_trigger().get_drawer_impl().scroll_items(upwards);
|
|
}
|
|
|
|
void float_listbox::move_items(bool upwards, bool circle)
|
|
{
|
|
get_drawer_trigger().get_drawer_impl().move_items(upwards, circle);
|
|
}
|
|
|
|
void float_listbox::renderer(item_renderer* ir)
|
|
{
|
|
auto & impl = get_drawer_trigger().get_drawer_impl();
|
|
impl.renderer(ir);
|
|
impl.resize();
|
|
}
|
|
|
|
std::size_t float_listbox::index() const
|
|
{
|
|
return get_drawer_trigger().get_drawer_impl().index();
|
|
}
|
|
//end class float_listbox
|
|
}
|