1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
|
/*
* Copyright (C) 2002-2016 by the Widelands Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "ui_basic/panel.h"
#include "base/log.h"
#include "graphic/font_handler1.h"
#include "graphic/graphic.h"
#include "graphic/rendertarget.h"
#include "graphic/text/font_set.h"
#include "graphic/text_constants.h"
#include "graphic/text_layout.h"
#include "profile/profile.h"
#include "sound/sound_handler.h"
#include "wlapplication.h"
using namespace std;
namespace UI {
Panel* Panel::modal_ = nullptr;
Panel* Panel::mousegrab_ = nullptr;
Panel* Panel::mousein_ = nullptr;
// The following variable can be set to false. If so, all mouse and keyboard
// events are ignored and not passed on to any widget. This is only useful
// for scripts that want to show off functionality without the user interfering.
bool Panel::allow_user_input_ = true;
const Image* Panel::default_cursor_ = nullptr;
const Image* Panel::default_cursor_click_ = nullptr;
/**
* Initialize a panel, link it into the parent's queue.
*/
Panel::Panel(Panel* const nparent,
const int nx,
const int ny,
const int nw,
const int nh,
const std::string& tooltip_text)
: parent_(nparent),
first_child_(nullptr),
last_child_(nullptr),
mousein_child_(nullptr),
focus_(nullptr),
flags_(pf_handle_mouse | pf_thinks | pf_visible),
x_(nx),
y_(ny),
w_(nw),
h_(nh),
lborder_(0),
rborder_(0),
tborder_(0),
bborder_(0),
border_snap_distance_(0),
panel_snap_distance_(0),
desired_w_(nw),
desired_h_(nh),
running_(false),
tooltip_(tooltip_text) {
assert(nparent != this);
if (parent_) {
next_ = parent_->first_child_;
prev_ = nullptr;
if (next_)
next_->prev_ = this;
else
parent_->last_child_ = this;
parent_->first_child_ = this;
} else
prev_ = next_ = nullptr;
}
/**
* Unlink the panel from the parent's queue
*/
Panel::~Panel() {
// Release pointers to this object
if (mousegrab_ == this)
mousegrab_ = nullptr;
if (mousein_ == this)
mousein_ = nullptr;
// Free children
free_children();
// Unlink
if (parent_) {
if (parent_->mousein_child_ == this)
parent_->mousein_child_ = nullptr;
if (parent_->focus_ == this)
parent_->focus_ = nullptr;
if (prev_)
prev_->next_ = next_;
else
parent_->first_child_ = next_;
if (next_)
next_->prev_ = prev_;
else
parent_->last_child_ = prev_;
}
}
/**
* Free all of the panel's children.
*/
void Panel::free_children() {
// Scan-build claims this results in double free.
// This is a false positive.
// See https://bugs.launchpad.net/widelands/+bug/1198928
while (first_child_)
delete first_child_;
}
/**
* Enters the event loop; all events will be handled by this panel.
*
* \return the return code passed to end_modal. This return code will be
* negative when the event loop was quit in an abnormal way (e.g. the user
* clicked the window's close button or similar).
*/
int Panel::do_run() {
// TODO(sirver): the main loop should not be in UI, but in WLApplication.
WLApplication* const app = WLApplication::get();
Panel* const prevmodal = modal_;
modal_ = this;
mousegrab_ = nullptr; // good ol' paranoia
app->set_mouse_lock(false); // more paranoia :-)
Panel* forefather = this;
while (Panel* const p = forefather->parent_)
forefather = p;
default_cursor_ = g_gr->images().get("images/ui_basic/cursor.png");
default_cursor_click_ = g_gr->images().get("images/ui_basic/cursor_click.png");
// Loop
running_ = true;
// Panel-specific startup code. This might call end_modal()!
start();
// think() is called at most 15 times per second, that is roughly ever 66ms.
const uint32_t kGameLogicDelay = 1000 / 15;
// With the default of 33FPS, the game will be drawn every 33ms.
const uint32_t draw_delay =
1000 / std::max(5, g_options.pull_section("global").get_int("maxfps", 30));
static InputCallback input_callback = {Panel::ui_mousepress, Panel::ui_mouserelease,
Panel::ui_mousemove, Panel::ui_key,
Panel::ui_textinput, Panel::ui_mousewheel};
const uint32_t initial_ticks = SDL_GetTicks();
uint32_t next_think_time = initial_ticks + kGameLogicDelay;
uint32_t next_draw_time = initial_ticks + draw_delay;
while (running_) {
const uint32_t start_time = SDL_GetTicks();
app->handle_input(&input_callback);
if (start_time >= next_think_time) {
if (app->should_die())
end_modal<Returncodes>(Returncodes::kBack);
do_think();
if (flags_ & pf_child_die)
check_child_death();
next_think_time = start_time + kGameLogicDelay;
}
if (start_time >= next_draw_time) {
RenderTarget& rt = *g_gr->get_render_target();
forefather->do_draw(rt);
rt.blit(app->get_mouse_position() - Point(3, 7), WLApplication::get()->is_mouse_pressed() ?
default_cursor_click_ :
default_cursor_);
forefather->do_tooltip();
g_gr->refresh();
next_draw_time = start_time + draw_delay;
}
int32_t delay = std::min<int32_t>(next_draw_time, next_think_time) - SDL_GetTicks();
if (delay > 0) {
SDL_Delay(delay);
}
}
end();
// Done
modal_ = prevmodal;
return return_code_;
}
/**
* \return \c true if this is the currently modal panel
*/
bool Panel::is_modal() {
return running_;
}
/**
* Called once before the event loop in run is started
*/
void Panel::start() {
}
/**
* Called once after the event loop in run() has ended
*/
void Panel::end() {
}
/**
* Resizes the panel.
*
* \note NEVER override this function. If you feel the urge to override this
* function, you probably want to override \ref layout.
*/
void Panel::set_size(const int nw, const int nh) {
if (nw == w_ && nh == h_)
return;
w_ = nw;
h_ = nh;
if (parent_)
move_inside_parent();
layout();
}
/**
* Move the panel. Panel's position is relative to the parent.
*/
void Panel::set_pos(const Point n) {
x_ = n.x;
y_ = n.y;
}
/**
* Set \p w and \p h to the desired
* width and height of this panel, respectively.
*/
void Panel::get_desired_size(int* w, int* h) const {
*w = desired_w_;
*h = desired_h_;
}
/**
* Set this panel's desired size and invoke the recursive update of the parent.
*
* \note The desired size of a panel must only depend on the attributes of this
* panel and its children that are not derived from layout routines.
* In particular, it must be independent of the panel's position on the screen
* or of its actual size.
*
* \note NEVER override this function
*/
void Panel::set_desired_size(int w, int h) {
if (desired_w_ == w && desired_h_ == h)
return;
assert(w < 3000);
assert(h < 3000);
desired_w_ = w;
desired_h_ = h;
if (!get_layout_toplevel() && parent_) {
parent_->update_desired_size();
} else {
set_size(desired_w_, desired_h_);
}
}
/**
* Recompute this panel's desired size.
*
* This is automatically called whenever a child panel's desired size changes.
*/
void Panel::update_desired_size() {
}
/**
* Set whether this panel acts as a layouting toplevel.
*
* Typically, only true for \ref Window.
*/
void Panel::set_layout_toplevel(bool ltl) {
flags_ &= ~pf_layout_toplevel;
if (ltl)
flags_ |= pf_layout_toplevel;
}
bool Panel::get_layout_toplevel() const {
return flags_ & pf_layout_toplevel;
}
/**
* Interpret \p pt as a point in the interior of this panel,
* and translate it into the interior coordinate system of the parent
* and return the result.
*/
Point Panel::to_parent(const Point& pt) const {
if (!parent_)
return pt;
return pt + Point(lborder_ + x_, tborder_ + y_);
}
/**
* Ensure the panel is inside the parent's visibile area.
*
* The default implementation does nothing, this is overridden
* by \ref Window
*/
void Panel::move_inside_parent() {
}
/**
* Automatically layout the children of this panel and adjust their size.
*
* \note This is always called when this panel's size is changed, so do not
* call \ref set_size from this function!
*
* The default implementation does nothing.
*/
void Panel::layout() {
}
/**
* Set the size of the inner area (total area minus border)
*/
void Panel::set_inner_size(int const nw, int const nh) {
set_size(nw + lborder_ + rborder_, nh + tborder_ + bborder_);
}
/**
* Change the border dimensions.
* Note that since position and total size aren't changed, so that the size
* and position of the inner area will change.
*/
void Panel::set_border(int l, int r, int t, int b) {
lborder_ = l;
rborder_ = r;
tborder_ = t;
bborder_ = b;
}
/**
* Make this panel the top-most panel in the parent's Z-order.
*/
void Panel::move_to_top() {
if (!parent_)
return;
// unlink
if (prev_)
prev_->next_ = next_;
else
parent_->first_child_ = next_;
if (next_)
next_->prev_ = prev_;
else
parent_->last_child_ = prev_;
// relink
prev_ = nullptr;
next_ = parent_->first_child_;
parent_->first_child_ = this;
if (next_)
next_->prev_ = this;
else
parent_->last_child_ = this;
}
/**
* Makes the panel visible or invisible
*/
void Panel::set_visible(bool const on) {
if (((flags_ & pf_visible) > 1) == on)
return;
flags_ &= ~pf_visible;
if (on)
flags_ |= pf_visible;
}
/**
* Redraw the panel. Note that all drawing coordinates are relative to the
* inner area: you cannot overwrite the panel border in this function.
*/
void Panel::draw(RenderTarget&) {
}
/**
* Redraw the panel border.
*/
void Panel::draw_border(RenderTarget&) {
}
/**
* Draw overlays that appear over all child panels.
* This can be used e.g. for debug information.
*/
void Panel::draw_overlay(RenderTarget&) {
}
/**
* Called once per event loop pass, unless set_think(false) has
* been called. It is intended to be used for animations and game logic.
*/
void Panel::think() {
}
/**
* Descend the panel hierarchy and call the \ref think() function of all
* (grand-)children for which set_thinks(false) has not been called.
*/
void Panel::do_think() {
if (thinks())
think();
for (Panel* child = first_child_; child; child = child->next_)
child->do_think();
}
/**
* Get mouse position relative to this panel
*/
Point Panel::get_mouse_position() const {
return (parent_ ? parent_->get_mouse_position() : WLApplication::get()->get_mouse_position()) -
Point(get_x() + get_lborder(), get_y() + get_tborder());
}
/**
* Set mouse position relative to this panel
*/
void Panel::set_mouse_pos(const Point p) {
const Point relative_p = p + Point(get_x() + get_lborder(), get_y() + get_tborder());
if (parent_)
parent_->set_mouse_pos(relative_p);
else
WLApplication::get()->warp_mouse(relative_p);
}
/**
* Center the mouse on this panel.
*/
void Panel::center_mouse() {
set_mouse_pos(Point(get_w() / 2, get_h() / 2));
}
/**
* Called whenever the mouse enters or leaves the panel. The inside state
* is relative to the outer area of a panel. This means that the mouse
* position received in handle_mousemove may be negative while the mouse is
* still inside the panel as far as handle_mousein is concerned.
*/
void Panel::handle_mousein(bool) {
}
/**
* Called whenever the user presses a mouse button in the panel.
* If the panel doesn't process the mouse-click, it is handed to the panel's
* parent.
*
* \return true if the mouseclick was processed, flase otherwise
*/
bool Panel::handle_mousepress(const uint8_t, int32_t, int32_t) {
return false;
}
/**
* Called whenever the user releases a mouse button in the panel.
* If the panel doesn't process the mouse-click, it is handed to the panel's
* parent.
*
* \return true if the mouseclick was processed, false otherwise
*/
bool Panel::handle_mouserelease(const uint8_t, int32_t, int32_t) {
return false;
}
/**
* Called whenever the user moves the mouse wheel.
* If the panel doesn't process the mouse-wheel, it is handed to the panel's
* parent.
*
* \return true if the mouseclick was processed, false otherwise
*/
bool Panel::handle_mousewheel(uint32_t, int32_t, int32_t) {
return false;
}
/**
* Called when the mouse is moved while inside the panel
*/
bool Panel::handle_mousemove(const uint8_t, int32_t, int32_t, int32_t, int32_t) {
return !tooltip_.empty();
}
bool Panel::handle_key(bool down, SDL_Keysym code) {
if (down) {
if (focus_) {
Panel* p = focus_->next_;
if (focus_ == last_child_) {
p = first_child_;
}
switch (code.sym) {
case SDLK_TAB:
while (p != focus_) {
if (p->get_can_focus()) {
p->focus();
break;
}
if (p == last_child_) {
p = first_child_;
} else {
p = p->next_;
}
}
return true;
default:
return false;
}
}
}
return false;
}
bool Panel::handle_textinput(const std::string& /* text */) {
return false;
}
/**
* Called whenever a tooltip could be drawn.
* Return true if the tooltip has been drawn,
* false otherwise.
*/
bool Panel::handle_tooltip() {
RenderTarget& rt = *g_gr->get_render_target();
return draw_tooltip(rt, tooltip());
}
/**
* Enable/Disable mouse handling by this panel
* Default is enabled. Note that when mouse handling is disabled, child panels
* don't receive mouse events either.
*
* \param yes true if the panel should receive mouse events
*/
void Panel::set_handle_mouse(bool const yes) {
if (yes)
flags_ |= pf_handle_mouse;
else
flags_ &= ~pf_handle_mouse;
}
/**
* Enable/Disable mouse grabbing. If a panel grabs the mouse, all mouse
* related events will be sent directly to that panel.
* You should only grab the mouse as a response to a mouse event (e.g.
* clicking a mouse button)
*
* \param grab true if the mouse should be grabbed
*/
void Panel::grab_mouse(bool const grab) {
if (grab) {
mousegrab_ = this;
} else {
assert(!mousegrab_ || mousegrab_ == this);
mousegrab_ = nullptr;
}
}
/**
* Set if this panel can receive the keyboard focus
*/
void Panel::set_can_focus(bool const yes) {
if (yes)
flags_ |= pf_can_focus;
else {
flags_ &= ~pf_can_focus;
if (parent_ && parent_->focus_ == this)
parent_->focus_ = nullptr;
}
}
/**
* Grabs the keyboard focus, if it can,
* topcaller identifies widget at the beginning of the recursion
*/
void Panel::focus(const bool topcaller) {
if (topcaller) {
if (handles_textinput()) {
if (!SDL_IsTextInputActive()) {
SDL_StartTextInput();
}
} else {
if (SDL_IsTextInputActive()) {
SDL_StopTextInput();
}
}
}
if (!parent_ || this == modal_) {
return;
}
if (parent_->focus_ == this)
return;
parent_->focus_ = this;
parent_->focus(false);
}
/**
* Enables/Disables calling think() during the event loop.
* The default is enabled.
*
* \param yes true if the panel's think function should be called
*/
void Panel::set_thinks(bool const yes) {
if (yes)
flags_ |= pf_thinks;
else
flags_ &= ~pf_thinks;
}
/**
* Cause this panel to be removed on the next frame.
* Use this for a panel that needs to destroy itself after a button has
* been pressed (e.g. non-modal dialogs).
* Do NOT use this to delete a hierarchy of panels that have been modal.
*/
void Panel::die() {
flags_ |= pf_die;
for (Panel* p = parent_; p; p = p->parent_) {
p->flags_ |= pf_child_die;
if (p == modal_)
break;
}
}
/**
* Wrapper around SoundHandler::play_fx() to prevent having to include
* sound_handler.h in every UI subclass just for playing a 'click'
*/
void Panel::play_click() {
g_sound_handler.play_fx("click", 128, PRIO_ALWAYS_PLAY);
}
void Panel::play_new_chat_message() {
g_sound_handler.play_fx("lobby_chat", 128, PRIO_ALWAYS_PLAY);
}
void Panel::play_new_chat_member() {
g_sound_handler.play_fx("lobby_freshmen", 128, PRIO_ALWAYS_PLAY);
}
/**
* Recursively walk the panel tree, killing panels that are marked for death
* using die().
*/
void Panel::check_child_death() {
Panel* next = first_child_;
while (next) {
Panel* p = next;
next = p->next_;
if (p->flags_ & pf_die)
delete p;
else if (p->flags_ & pf_child_die)
p->check_child_death();
}
flags_ &= ~pf_child_die;
}
/**
* Draw the inner region of the panel into the given target.
*
* \param dst target to render into, assumed to be prepared for the panel's
* inner coordinate system.
*/
void Panel::do_draw_inner(RenderTarget& dst) {
draw(dst);
// draw back to front
for (Panel* child = last_child_; child; child = child->prev_)
child->do_draw(dst);
draw_overlay(dst);
}
/**
* Subset for the border first and draw the border, then subset for the inner
* area and draw the inner area.
* Draw child panels after drawing self.
* Draw tooltip if required.
*
* \param dst RenderTarget for the parent Panel
*/
void Panel::do_draw(RenderTarget& dst) {
if (!is_visible())
return;
Rect outerrc;
Point outerofs;
if (!dst.enter_window(Rect(Point(x_, y_), w_, h_), &outerrc, &outerofs))
return;
draw_border(dst);
Rect innerwindow(
Point(lborder_, tborder_), w_ - (lborder_ + rborder_), h_ - (tborder_ + bborder_));
if (dst.enter_window(innerwindow, nullptr, nullptr))
do_draw_inner(dst);
dst.set_window(outerrc, outerofs);
}
/**
* Returns the child panel that receives mouse events at the given location.
* Starts the search with child (which should usually be set to first_child_) and
* returns the first match.
*/
inline Panel* Panel::child_at_mouse_cursor(int32_t const x, int32_t const y, Panel* child) {
for (; child; child = child->next_) {
if (!child->handles_mouse() || !child->is_visible())
continue;
if (x < child->x_ + static_cast<int32_t>(child->w_) && x >= child->x_ &&
y < child->y_ + static_cast<int32_t>(child->h_) && y >= child->y_)
break;
}
if (mousein_child_ && mousein_child_ != child)
mousein_child_->do_mousein(false);
mousein_child_ = child;
if (child)
child->do_mousein(true);
return child;
}
/**
* Propagate mouseleave events (e.g. for buttons that are inside a different
* window)
*/
void Panel::do_mousein(bool const inside) {
if (!inside && mousein_child_) {
mousein_child_->do_mousein(false);
mousein_child_ = nullptr;
}
handle_mousein(inside);
}
/**
* Propagate mousepresses/-releases/-moves to the appropriate panel.
*
* Returns whether the event was processed.
*/
bool Panel::do_mousepress(const uint8_t btn, int32_t x, int32_t y) {
if (get_can_focus()) {
focus();
}
x -= lborder_;
y -= tborder_;
if (flags_ & pf_top_on_click)
move_to_top();
if (mousegrab_ != this)
for (Panel* child = first_child_; (child = child_at_mouse_cursor(x, y, child));
child = child->next_) {
if (child->do_mousepress(btn, x - child->x_, y - child->y_))
return true;
}
return handle_mousepress(btn, x, y);
}
bool Panel::do_mousewheel(uint32_t which, int32_t x, int32_t y, Point rel_mouse_pos) {
// Check if a child-panel is beneath the mouse and processes the event
for (Panel* child = first_child_; child; child = child->next_) {
if (!child->handles_mouse() || !child->is_visible()) {
continue;
}
if (rel_mouse_pos.x >= child->x_ + static_cast<int32_t>(child->w_) ||
rel_mouse_pos.x < child->x_ ||
rel_mouse_pos.y >= child->y_ + static_cast<int32_t>(child->h_) ||
rel_mouse_pos.y < child->y_) {
continue;
}
// Found a child at the position
if (child->do_mousewheel(
which, x, y, rel_mouse_pos - Point(child->get_x() + child->get_lborder(),
child->get_y() + child->get_tborder()))) {
return true;
}
// Break after the first hit panel in the list. The panels are ordered from top to bottom,
// so only the highest window at the current mouse coordinates receives the event
break;
}
return handle_mousewheel(which, x, y);
}
bool Panel::do_mouserelease(const uint8_t btn, int32_t x, int32_t y) {
x -= lborder_;
y -= tborder_;
if (mousegrab_ != this)
for (Panel* child = first_child_; (child = child_at_mouse_cursor(x, y, child));
child = child->next_)
if (child->do_mouserelease(btn, x - child->x_, y - child->y_))
return true;
return handle_mouserelease(btn, x, y);
}
bool Panel::do_mousemove(
uint8_t const state, int32_t x, int32_t y, int32_t const xdiff, int32_t const ydiff) {
x -= lborder_;
y -= tborder_;
if (mousegrab_ != this) {
for (Panel* child = first_child_; (child = child_at_mouse_cursor(x, y, child));
child = child->next_) {
if (child->do_mousemove(state, x - child->x_, y - child->y_, xdiff, ydiff)) {
return true;
}
}
}
return handle_mousemove(state, x, y, xdiff, ydiff);
}
/**
* Pass the key event to the focused child.
* If it doesn't process the key, we'll see if we can use the event.
*/
bool Panel::do_key(bool const down, SDL_Keysym const code) {
if (focus_ && focus_->do_key(down, code)) {
return true;
}
// If we handle text, it does not matter if we handled this key
// or not, it should not propagate.
if (handle_key(down, code) || handles_textinput()) {
return true;
}
return false;
}
bool Panel::do_textinput(const std::string& text) {
if (focus_ && focus_->do_textinput(text)) {
return true;
}
if (!handles_textinput()) {
return false;
}
return handle_textinput(text);
}
bool Panel::do_tooltip() {
if (mousein_child_ && mousein_child_->do_tooltip()) {
return true;
}
return handle_tooltip();
}
/**
* \return \c true if the given key is currently pressed, or \c false otherwise
*/
bool Panel::get_key_state(const SDL_Scancode key) const {
return WLApplication::get()->get_key_state(key);
}
/**
* Determine which panel is to receive a mouse event.
*
* \return The panel which receives the mouse event
*/
Panel* Panel::ui_trackmouse(int32_t& x, int32_t& y) {
Panel* mousein;
Panel* rcv = nullptr;
if (mousegrab_)
mousein = rcv = mousegrab_;
else
mousein = modal_;
x -= mousein->x_;
y -= mousein->y_;
for (Panel* p = mousein->parent_; p; p = p->parent_) {
x -= p->lborder_ + p->x_;
y -= p->tborder_ + p->y_;
}
if (0 <= x && x < static_cast<int32_t>(mousein->w_) && 0 <= y &&
y < static_cast<int32_t>(mousein->h_))
rcv = mousein;
else
mousein = nullptr;
if (mousein != mousein_) {
if (mousein_)
mousein_->do_mousein(false);
mousein_ = mousein;
if (mousein_)
mousein_->do_mousein(true);
}
return rcv;
}
/**
* Input callback function. Pass the mouseclick event to the currently modal
* panel.
*/
bool Panel::ui_mousepress(const uint8_t button, int32_t x, int32_t y) {
if (!allow_user_input_) {
return true;
}
Panel* const p = ui_trackmouse(x, y);
if (p == nullptr) {
return false;
}
return p->do_mousepress(button, x, y);
}
bool Panel::ui_mouserelease(const uint8_t button, int32_t x, int32_t y) {
if (!allow_user_input_) {
return true;
}
Panel* const p = ui_trackmouse(x, y);
if (p == nullptr) {
return false;
}
return p->do_mouserelease(button, x, y);
}
/**
* Input callback function. Pass the mousemove event to the currently modal
* panel.
*/
bool Panel::ui_mousemove(
uint8_t const state, int32_t x, int32_t y, int32_t const xdiff, int32_t const ydiff) {
if (!allow_user_input_) {
return true;
}
if (!xdiff && !ydiff) {
return true;
}
Panel* const p = ui_trackmouse(x, y);
if (p == nullptr)
return false;
return p->do_mousemove(state, x, y, xdiff, ydiff);
}
/**
* Input callback function. Pass the mousewheel event to the currently modal
* panel.
*/
bool Panel::ui_mousewheel(uint32_t which, int32_t x, int32_t y) {
if (!allow_user_input_) {
return true;
}
if (!x && !y) {
return true;
}
Panel* p = nullptr;
if (mousein_) {
p = mousein_;
} else {
p = mousegrab_ ? mousegrab_ : modal_;
}
if (!p) {
return false;
}
return p->do_mousewheel(which, x, y, p->get_mouse_position());
}
/**
* Input callback function. Pass the key event to the currently modal panel
*/
bool Panel::ui_key(bool const down, SDL_Keysym const code) {
if (!allow_user_input_) {
return true;
}
return modal_->do_key(down, code);
}
/**
* Input callback function. Pass the textinput event to the currently modal panel
*/
bool Panel::ui_textinput(const std::string& text) {
if (!allow_user_input_) {
return true;
}
return modal_->do_textinput(text);
}
/**
* Draw the tooltip. Return true on success
*/
bool Panel::draw_tooltip(RenderTarget& dst, const std::string& text) {
if (text.empty()) {
return false;
}
std::string text_to_render = text;
if (!is_richtext(text_to_render)) {
text_to_render = as_tooltip(text);
}
static const uint32_t TIP_WIDTH_MAX = 360;
const Image* rendered_text = g_fh1->render(text_to_render, TIP_WIDTH_MAX);
if (!rendered_text) {
return false;
}
uint16_t tip_width = rendered_text->width() + 4;
uint16_t tip_height = rendered_text->height() + 4;
Rect r(WLApplication::get()->get_mouse_position() + Point(2, 32), tip_width, tip_height);
const Point tooltip_bottom_right = r.opposite_of_origin();
const Point screen_bottom_right(g_gr->get_xres(), g_gr->get_yres());
if (screen_bottom_right.x < tooltip_bottom_right.x)
r.x -= 4 + r.w;
if (screen_bottom_right.y < tooltip_bottom_right.y)
r.y -= 35 + r.h;
dst.fill_rect(r, RGBColor(63, 52, 34));
dst.draw_rect(r, RGBColor(0, 0, 0));
dst.blit(r.origin() + Point(2, 2), rendered_text);
return true;
}
}
|