Scrollbars are finally a first-class citizen in the dialog engine.

- Add <slider> tag to put an unbound scrollbar in the dialog
- Add support for horizontal scrollbars
This commit is contained in:
2015-10-02 21:07:32 -04:00
parent 5f7654046d
commit f7daba4ead
8 changed files with 235 additions and 44 deletions

View File

@@ -231,6 +231,8 @@ void cDialog::loadFromFile(std::string path){
controls.insert(parse<cTextMsg>(*node));
else if(type == "pict")
controls.insert(parse<cPict>(*node));
else if(type == "slider")
controls.insert(parse<cScrollbar>(*node));
else if(type == "button")
controls.insert(parse<cButton>(*node));
else if(type == "led")

View File

@@ -38,6 +38,10 @@ void cScrollbar::setPageSize(long newSize) {
pgsz = newSize;
}
void cScrollbar::setVertical(bool is) {
vert = is;
}
long cScrollbar::getPosition() {
return pos;
}
@@ -50,6 +54,10 @@ long cScrollbar::getPageSize() {
return pgsz;
}
bool cScrollbar::isVertical() {
return vert;
}
void cScrollbar::attachClickHandler(click_callback_t f) throw(xHandlerNotSupported) {
onClick = f;
}
@@ -69,23 +77,28 @@ bool cScrollbar::handleClick(location where) {
sf::Event e;
bool done = false, clicked = false;
depressed = true;
int bar_height = frame.height() - 32;
rectangle thumbRect = frame;
thumbRect.top += 16 + pos * (bar_height - 16) / max;
thumbRect.height() = 16;
if(where.y < frame.top + 16)
int btn_size = 16;
int total_bar_size = vert ? frame.height() : frame.width();
int bar_start = vert ? frame.top : frame.left;
int bar_end = vert ? frame.bottom : frame.right;
int bar_size = total_bar_size - btn_size * 2;
int thumbPos = bar_start + btn_size + pos * (bar_size - btn_size) / max;
int clickPos = vert ? where.y : where.x;
if(clickPos < bar_start + btn_size)
pressedPart = PART_UP;
else if(where.y < thumbRect.top)
else if(clickPos < thumbPos)
pressedPart = PART_PGUP;
else if(where.y < thumbRect.bottom)
else if(clickPos < thumbPos + btn_size)
pressedPart = PART_THUMB;
else if(where.y < frame.bottom - 16)
else if(clickPos < bar_end - btn_size)
pressedPart = PART_PGDN;
else pressedPart = PART_DOWN;
int dy = where.y - thumbRect.top;
int diff = clickPos - thumbPos;
while(!done){
redraw();
if(!inWindow->pollEvent(e)) continue;
sf::Vector2i mouseLoc = sf::Mouse::getPosition(*inWindow);
int mousePos = vert ? mouseLoc.y : mouseLoc.x;
if(e.type == sf::Event::MouseButtonReleased){
done = true;
clicked = frame.contains(e.mouseButton.x, e.mouseButton.y);
@@ -101,32 +114,31 @@ bool cScrollbar::handleClick(location where) {
restore_cursor();
switch(pressedPart) {
case PART_UP:
depressed = e.mouseMove.y < frame.top + 16;
depressed = mousePos < bar_start + btn_size;
break;
case PART_PGUP:
depressed = e.mouseMove.y >= frame.top + 16 && e.mouseMove.y < thumbRect.top;
depressed = mousePos >= bar_start + btn_size && mousePos < thumbPos;
break;
case PART_THUMB:
depressed = true;
// We want the pos that will make thumbRect.top = mousePos.y - dy
// In draw(), thumbRect.top is calculated as frame.top + 16 + pos * (bar_height - 16) / max
// So solving for pos gives (mousePos.y - dy - frame.top - 16) * max / (bar_height - 16)
pos = (sf::Mouse::getPosition(*inWindow).y - dy - frame.top - 16) * max / (bar_height - 16);
// We want the pos that will make thumbPos = mousePos - diff
// In draw(), thumbPos is calculated as bar_start + bar_thickness + pos * (bar_size - bar_thickness) / max
// So solving for pos gives (mousePos - diff - bar_start - bar_thickness) * max / (bar_size - bar_thickness)
pos = (mousePos - diff - bar_start - btn_size) * max / (bar_size - btn_size);
break;
case PART_PGDN:
depressed = e.mouseMove.y >= thumbRect.bottom && e.mouseMove.y < frame.bottom - 16;
depressed = mousePos >= thumbPos + btn_size && mousePos < bar_end - btn_size;
break;
case PART_DOWN:
depressed = e.mouseMove.y >= frame.bottom - 16;
depressed = mousePos >= bar_end - btn_size;
break;
}
if(pressedPart != PART_THUMB && !frame.contains(e.mouseMove.x, e.mouseMove.y)) depressed = false;
}
pos = minmax(0,max,pos);
thumbRect.top = frame.top;
thumbRect.top += 16 + pos * (bar_height - 16) / max;
thumbRect.top = minmax(sf::Mouse::getPosition(*inWindow).y,frame.bottom - 32,thumbRect.top);
thumbRect.height() = 16;
thumbPos = bar_start;
thumbPos += btn_size + pos * (bar_size - btn_size) / max;
thumbPos = minmax(mousePos,bar_end - btn_size * 2,thumbPos);
}
redraw();
return clicked;
@@ -149,24 +161,92 @@ sf::Color cScrollbar::getColour() throw(xUnsupportedProp) {
return sf::Color();
}
void cScrollbar::draw() {
if(!isVisible()) return;
static const rectangle up_rect = {0,0,16,16}, down_rect = {0,16,16,32}, thumb_rect = {0,32,16,48}, bar_rect = {0,48,16,64};
int bar_height = frame.height() - 32;
// Constants to index the rect arrays
enum {
VERT, VERT_PRESSED, HORZ, HORZ_PRESSED
};
const rectangle cScrollbar::up_rect[4] = {
{0,0,16,16}, {16,0,32,16}, {32,0,48,16}, {48,0,64,16}
};
const rectangle cScrollbar::down_rect[4] = {
{0,16,16,32}, {16,16,32,32}, {32,16,48,32}, {48,16,64,32}
};
const rectangle cScrollbar::thumb_rect[4] = {
{0,32,16,48}, {16,32,32,48}, {32,32,48,48}, {48,32,64,48}
};
const rectangle cScrollbar::bar_rect[4] = {
{0,48,16,64}, {16,48,32,64}, {32,48,48,64}, {48,48,64,64}
};
void cScrollbar::draw_horizontal() {
int btn_size = 16;
int bar_width = frame.width() - btn_size * 2;
inWindow->setActive();
rectangle draw_rect = frame, from_rect = up_rect;
draw_rect.height() = 16;
rectangle draw_rect = frame, from_rect = up_rect[HORZ];
draw_rect.width() = btn_size;
if(depressed && pressedPart == PART_UP)
from_rect.offset(0,16);
from_rect = up_rect[HORZ_PRESSED];
rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect);
if(pos > 0) {
from_rect = bar_rect;
int top = draw_rect.bottom, height = pos * (bar_height - 16) / max;
from_rect = bar_rect[HORZ];
int left = draw_rect.right, width = pos * (bar_width - btn_size) / max;
if(depressed && pressedPart == PART_PGUP)
from_rect.offset(0,16);
from_rect = bar_rect[HORZ_PRESSED];
draw_rect.left = left;
while(draw_rect.left - left < width) {
draw_rect.right = draw_rect.left + btn_size;
rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect);
draw_rect.left = draw_rect.right;
}
draw_rect.right = left + width;
}
if(max > 0) {
draw_rect.left = draw_rect.right;
draw_rect.width() = btn_size;
from_rect = thumb_rect[HORZ];
if(depressed && pressedPart == PART_THUMB)
from_rect = thumb_rect[HORZ_PRESSED];
rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect);
}
if(pos < max || max == 0) {
from_rect = bar_rect[HORZ];
int left = draw_rect.right, right = frame.right - btn_size;
if(depressed && pressedPart == PART_PGDN)
from_rect = bar_rect[HORZ_PRESSED];
draw_rect.left = left;
while(draw_rect.left < right) {
draw_rect.right = draw_rect.left + btn_size;
rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect);
draw_rect.left = draw_rect.right;
}
draw_rect.right = right;
}
draw_rect = frame;
draw_rect.left = draw_rect.right - btn_size;
from_rect = down_rect[HORZ];
if(depressed && pressedPart == PART_DOWN)
from_rect = down_rect[HORZ_PRESSED];
rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect);
}
void cScrollbar::draw_vertical() {
int btn_size = 16;
int bar_height = frame.height() - btn_size * 2;
inWindow->setActive();
rectangle draw_rect = frame, from_rect = up_rect[VERT];
draw_rect.height() = btn_size;
if(depressed && pressedPart == PART_UP)
from_rect = up_rect[VERT_PRESSED];
rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect);
if(pos > 0) {
from_rect = bar_rect[VERT];
int top = draw_rect.bottom, height = pos * (bar_height - btn_size) / max;
if(depressed && pressedPart == PART_PGUP)
from_rect = bar_rect[VERT_PRESSED];
draw_rect.top = top;
while(draw_rect.top - top < height) {
draw_rect.bottom = draw_rect.top + 16;
draw_rect.bottom = draw_rect.top + btn_size;
rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect);
draw_rect.top = draw_rect.bottom;
}
@@ -174,37 +254,98 @@ void cScrollbar::draw() {
}
if(max > 0) {
draw_rect.top = draw_rect.bottom;
draw_rect.height() = 16;
from_rect = thumb_rect;
draw_rect.height() = btn_size;
from_rect = thumb_rect[VERT];
if(depressed && pressedPart == PART_THUMB)
from_rect.offset(0,16);
from_rect = thumb_rect[VERT_PRESSED];
rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect);
}
if(pos < max || max == 0) {
from_rect = bar_rect;
int top = draw_rect.bottom, bottom = frame.bottom - 16;
from_rect = bar_rect[VERT];
int top = draw_rect.bottom, bottom = frame.bottom - btn_size;
if(depressed && pressedPart == PART_PGDN)
from_rect.offset(0,16);
from_rect = bar_rect[VERT_PRESSED];
draw_rect.top = top;
while(draw_rect.top < bottom) {
draw_rect.bottom = draw_rect.top + 16;
draw_rect.bottom = draw_rect.top + btn_size;
rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect);
draw_rect.top = draw_rect.bottom;
}
draw_rect.bottom = bottom;
}
draw_rect = frame;
draw_rect.top = draw_rect.bottom - 16;
from_rect = down_rect;
draw_rect.top = draw_rect.bottom - btn_size;
from_rect = down_rect[VERT];
if(depressed && pressedPart == PART_DOWN)
from_rect.offset(0,16);
from_rect = down_rect[VERT_PRESSED];
rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect);
}
void cScrollbar::draw() {
if(!isVisible()) return;
if(vert) draw_vertical();
else draw_horizontal();
}
std::string cScrollbar::parse(ticpp::Element& who, std::string fname) {
using namespace ticpp;
// TODO: Parse scrollbars
return "";
Iterator<Attribute> attr;
std::string name, id;
bool foundTop = false, foundLeft = false, foundMax = false; // required attributes
bool foundVertical = false;
rectangle frame;
int width = 0, height = 0;
for(attr = attr.begin(&who); attr != attr.end(); attr++){
attr->GetName(&name);
if(name == "name")
attr->GetValue(&id);
else if(name == "vertical"){
std::string val;
attr->GetValue(&val);
if(val == "true") vert = true;
else vert = false;
foundVertical = true;
}else if(name == "initial"){
attr->GetValue(&pos);
}else if(name == "max"){
attr->GetValue(&max);
foundMax = true;
}else if(name == "page-size"){
attr->GetValue(&pgsz);
}else if(name == "top"){
attr->GetValue(&frame.top), foundTop = true;
}else if(name == "left"){
attr->GetValue(&frame.left), foundLeft = true;
}else if(name == "width"){
attr->GetValue(&width);
}else if(name == "height"){
attr->GetValue(&height);
}else throw xBadAttr("slider",name,attr->Row(),attr->Column(),fname);
}
if(!foundMax) throw xMissingAttr("slider","num",who.Row(),who.Column(),fname);
if(!foundTop) throw xMissingAttr("slider","top",who.Row(),who.Column(),fname);
if(!foundLeft) throw xMissingAttr("slider","left",who.Row(),who.Column(),fname);
if(pos > max) throw xBadAttr("slider","initial",who.Row(),who.Column(),fname);
int thickness = 16;
if(width > 0) {
frame.right = frame.left + width;
if(height == 0 && !foundVertical)
vert = false;
}else{
if(vert) frame.width() = thickness;
else frame.width() = 25;
}
if(height > 0) {
frame.bottom = frame.top + height;
if(width == 0 && !foundVertical)
vert = true;
}else{
frame.bottom = frame.top + 10;
if(vert) frame.height() = 25;
else frame.height() = thickness;
}
setBounds(frame);
return id;
}
cControl::storage_t cScrollbar::store() {

View File

@@ -18,8 +18,10 @@
/// A simple vertical scrollbar.
/// This has no coupling with scrollable data; that must be handled externally by
/// using the methods to get the scrollbar's position.
/// Alternatively, it can be used as a slider control.
class cScrollbar : public cControl {
int pos, max, pgsz;
// Note: For horizontal scrollbars, up is left and down is right.
enum {
PART_UP,
PART_PGUP,
@@ -27,8 +29,11 @@ class cScrollbar : public cControl {
PART_PGDN,
PART_DOWN,
} pressedPart;
bool vert = true;
click_callback_t onClick;
static sf::Texture scroll_gw;
static const rectangle up_rect[4], down_rect[4], bar_rect[4], thumb_rect[4];
void draw_vertical(), draw_horizontal();
public:
/// @copydoc cDialog::init()
static void init();
@@ -62,6 +67,9 @@ public:
/// The page size is the number of steps scrolled when a click is received
/// in the area between the arrow buttons and the scrollbar thumb.
long getPageSize();
/// Check whether the scrollbar is vertical.
/// @return True if it is vertical, false if it is horizontal.
bool isVertical();
/// Set the scrollbar thumb's current position.
/// @param to The new position.
void setPosition(long to);
@@ -74,6 +82,9 @@ public:
/// The page size is the number of steps scrolled when a click is received
/// in the area between the arrow buttons and the scrollbar thumb.
void setPageSize(long to);
/// Set whether the scrollbar is vertical.
/// @param is True to make it vertical, false to make it horizontal.
void setVertical(bool is);
void draw();
cScrollbar& operator=(cScrollbar& other) = delete;
cScrollbar(cScrollbar& other) = delete;

View File

@@ -210,6 +210,9 @@ std::string cScrollPane::parse(ticpp::Element& who, std::string fname) {
} else if(val == "pict") {
auto pict = parent->parse<cPict>(*node);
contents.insert(pict);
} else if(val == "slider") {
auto slide = parent->parse<cScrollbar>(*node);
contents.insert(slide);
} else if(val == "button") {
auto button = parent->parse<cButton>(*node);
contents.insert(button);

View File

@@ -11,6 +11,7 @@
#include "button.hpp"
#include "message.hpp"
#include "pict.hpp"
#include "scrollbar.hpp"
void cStack::attachClickHandler(click_callback_t f) throw(xHandlerNotSupported) {
onClick = f;
@@ -189,6 +190,10 @@ std::string cStack::parse(ticpp::Element& who, std::string fname) {
auto pict = parent->parse<cPict>(*node);
parent->add(pict.second, pict.second->getBounds(), pict.first);
stack.push_back(pict.first);
} else if(val == "slider") {
auto slide = parent->parse<cScrollbar>(*node);
parent->add(slide.second, slide.second->getBounds(), slide.first);
stack.push_back(slide.first);
} else if(val == "button") {
auto button = parent->parse<cButton>(*node);
parent->add(button.second, button.second->getBounds(), button.first);

View File

@@ -205,6 +205,22 @@ The `<field>` tag accepts the following attributes:
* `tab-order` - Specifies the tab order of the field. This must be an
integer that is unique in the dialog.
The `<slider>` tag
------------------
The `<slider>` tag defines a slider control (presented with a scrollbar).
The `<slider>` tag accepts the following attributes:
* `vertical` - Specifies whether the slider should be shown horizontally
or vertically. Usually defaults to true, but if `width` is provided and
`height` is not, then it will default to false.
* `initial` - Specifies the initial value of the slider. Must be a value
from 0 to `max` inclusive. Defaults to 0.
* `max` - Specifies the maximum value of the slider. This attribute is required.
* `page-size` - Specifies how much the slider should scroll when clicking
between the thumb and the arrow button. Defaults to 10.
The `<stack>` tag
-----------------