diff options
Diffstat (limited to 'engines/sherlock/tattoo/widget_base.cpp')
-rw-r--r-- | engines/sherlock/tattoo/widget_base.cpp | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/engines/sherlock/tattoo/widget_base.cpp b/engines/sherlock/tattoo/widget_base.cpp new file mode 100644 index 0000000000..9e10cee0d1 --- /dev/null +++ b/engines/sherlock/tattoo/widget_base.cpp @@ -0,0 +1,375 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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 "sherlock/tattoo/widget_base.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_talk.h" +#include "sherlock/tattoo/tattoo_user_interface.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetBase::WidgetBase(SherlockEngine *vm) : _vm(vm) { + _scroll = false; + _dialogTimer = 0; +} + +void WidgetBase::summonWindow() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + // Double-check that the same widget isn't added twice + if (ui._widgets.contains(this)) + error("Tried to add a widget multiple times"); + + // Add widget to the screen + if (!ui._fixedWidgets.contains(this)) + ui._widgets.push_back(this); + ui._windowOpen = true; + + _outsideMenu = false; + + draw(); +} + +void WidgetBase::banishWindow() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + erase(); + _surface.free(); + ui._widgets.remove(this); + ui._windowOpen = false; +} + +void WidgetBase::close() { + Events &events = *_vm->_events; + TattooScene &scene = *(TattooScene *)_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + banishWindow(); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + events.clearEvents(); +} + +bool WidgetBase::active() const { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + for (Common::List<WidgetBase *>::iterator i = ui._widgets.begin(); i != ui._widgets.end(); ++i) { + if ((*i) == this) + return true; + } + + return false; +} + + +void WidgetBase::erase() { + Screen &screen = *_vm->_screen; + + if (_oldBounds.width() > 0) { + // Restore the affected area from the secondary back buffer into the first one, and then copy to screen + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds); + screen.slamRect(_oldBounds); + + // Reset the old bounds so it won't be erased again + _oldBounds = Common::Rect(0, 0, 0, 0); + } +} + +void WidgetBase::draw() { + Screen &screen = *_vm->_screen; + + // If there was a previously drawn frame in a different position that hasn't yet been erased, then erase it + if (_oldBounds.width() > 0 && _oldBounds != _bounds) + erase(); + + if (_bounds.width() > 0 && !_surface.empty()) { + // Get the area to draw, adjusted for scroll position + restrictToScreen(); + + // Draw the background for the widget + drawBackground(); + + // Draw the widget onto the back buffer and then slam it to the screen + screen._backBuffer1.transBlitFrom(_surface, Common::Point(_bounds.left, _bounds.top)); + screen.slamRect(_bounds); + + // Store a copy of the drawn area for later erasing + _oldBounds = _bounds; + } +} + +void WidgetBase::drawBackground() { + TattooEngine &vm = *(TattooEngine *)_vm; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + Common::Rect bounds = _bounds; + + if (vm._transparentMenus) { + ui.makeBGArea(bounds); + } else { + bounds.grow(-3); + screen._backBuffer1.fillRect(bounds, MENU_BACKGROUND); + } +} + +Common::String WidgetBase::splitLines(const Common::String &str, Common::StringArray &lines, int maxWidth, uint maxLines) { + Talk &talk = *_vm->_talk; + const char *strP = str.c_str(); + + // Loop counting up lines + lines.clear(); + do { + int width = 0; + const char *spaceP = nullptr; + const char *lineStartP = strP; + + // Find how many characters will fit on the next line + while (width < maxWidth && *strP && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER] || + (byte)*strP == talk._opcodes[OP_NULL])) { + width += _surface.charWidth(*strP); + + // Keep track of the last space + if (*strP == ' ') + spaceP = strP; + ++strP; + } + + // If the line was too wide to fit on a single line, go back to the last space + // if there was one, or otherwise simply break the line at this point + if (width >= maxWidth && spaceP != nullptr) + strP = spaceP; + + // Add the line to the output array + lines.push_back(Common::String(lineStartP, strP)); + + // Move the string ahead to the next line + if (*strP == ' ' || *strP == 13) + ++strP; + } while (*strP && (lines.size() < maxLines) && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER] + || (byte)*strP == talk._opcodes[OP_NULL])); + + // Return any remaining text left over + return *strP ? Common::String(strP) : Common::String(); +} + +void WidgetBase::restrictToScreen() { + Screen &screen = *_vm->_screen; + + if (_bounds.left < screen._currentScroll.x) + _bounds.moveTo(screen._currentScroll.x, _bounds.top); + if (_bounds.top < 0) + _bounds.moveTo(_bounds.left, 0); + if (_bounds.right > (screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH)) + _bounds.moveTo(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH - _bounds.width(), _bounds.top); + if (_bounds.bottom > screen._backBuffer1.h()) + _bounds.moveTo(_bounds.left, screen._backBuffer1.h() - _bounds.height()); +} + +void WidgetBase::makeInfoArea(Surface &s) { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + + // Draw the four corners of the Info Box + s.transBlitFrom(images[0], Common::Point(0, 0)); + s.transBlitFrom(images[1], Common::Point(s.w() - images[1]._width, 0)); + s.transBlitFrom(images[2], Common::Point(0, s.h() - images[2]._height)); + s.transBlitFrom(images[3], Common::Point(s.w() - images[3]._width, s.h())); + + // Draw the top of the Info Box + s.hLine(images[0]._width, 0, s.w() - images[1]._width, INFO_TOP); + s.hLine(images[0]._width, 1, s.w() - images[1]._width, INFO_MIDDLE); + s.hLine(images[0]._width, 2, s.w() - images[1]._width, INFO_BOTTOM); + + // Draw the bottom of the Info Box + s.hLine(images[0]._width, s.h()- 3, s.w() - images[1]._width, INFO_TOP); + s.hLine(images[0]._width, s.h()- 2, s.w() - images[1]._width, INFO_MIDDLE); + s.hLine(images[0]._width, s.h()- 1, s.w() - images[1]._width, INFO_BOTTOM); + + // Draw the left Side of the Info Box + s.vLine(0, images[0]._height, s.h()- images[2]._height, INFO_TOP); + s.vLine(1, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE); + s.vLine(2, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM); + + // Draw the right Side of the Info Box + s.vLine(s.w() - 3, images[0]._height, s.h()- images[2]._height, INFO_TOP); + s.vLine(s.w() - 2, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE); + s.vLine(s.w() - 1, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM); +} + +void WidgetBase::makeInfoArea() { + makeInfoArea(_surface); +} + +void WidgetBase::drawDialogRect(const Common::Rect &r, bool raised) { + static_cast<TattooUserInterface *>(_vm->_ui)->drawDialogRect(_surface, r, raised); +} + +void WidgetBase::checkTabbingKeys(int numOptions) { +} + +Common::Rect WidgetBase::getScrollBarBounds() const { + Common::Rect r(BUTTON_SIZE, _bounds.height() - 6); + r.moveTo(_bounds.width() - BUTTON_SIZE - 3, 3); + return r; +} + +void WidgetBase::drawScrollBar(int index, int pageSize, int count) { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + // Fill the area with transparency + Common::Rect r = getScrollBarBounds(); + _surface.fillRect(r, TRANSPARENCY); + + bool raised = ui._scrollHighlight != 1; + _surface.fillRect(Common::Rect(r.left + 2, r.top + 2, r.right - 2, r.top + BUTTON_SIZE - 2), INFO_MIDDLE); + ui.drawDialogRect(_surface, Common::Rect(r.left, r.top, r.left + BUTTON_SIZE, r.top + BUTTON_SIZE), raised); + + raised = ui._scrollHighlight != 5; + _surface.fillRect(Common::Rect(r.left + 2, r.bottom - BUTTON_SIZE + 2, r.right - 2, r.bottom - 2), INFO_MIDDLE); + ui.drawDialogRect(_surface, Common::Rect(r.left, r.bottom - BUTTON_SIZE, r.right, r.bottom), raised); + + // Draw the arrows on the scroll buttons + byte color = index ? INFO_BOTTOM + 2 : INFO_BOTTOM; + _surface.hLine(r.left + r.width() / 2, r.top - 2 + BUTTON_SIZE / 2, r.left + r.width() / 2, color); + _surface.hLine(r.left + r.width() / 2 - 1, r.top - 1 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 1, color); + _surface.hLine(r.left + r.width() / 2 - 2, r.top + BUTTON_SIZE / 2, r.left + r.width() / 2 + 2, color); + _surface.hLine(r.left + r.width() / 2 - 3, r.top + 1 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 3, color); + + color = (index + pageSize) < count ? INFO_BOTTOM + 2 : INFO_BOTTOM; + _surface.hLine(r.left + r.width() / 2 - 3, r.bottom - 1 - BUTTON_SIZE + BUTTON_SIZE / 2, r.left + r.width() / 2 + 3, color); + _surface.hLine(r.left + r.width() / 2 - 2, r.bottom - 1 - BUTTON_SIZE + 1 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 2, color); + _surface.hLine(r.left + r.width() / 2 - 1, r.bottom - 1 - BUTTON_SIZE + 2 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 1, color); + _surface.hLine(r.left + r.width() / 2, r.bottom - 1 - BUTTON_SIZE + 3 + BUTTON_SIZE / 2, r.left + r.width() / 2, color); + + // Draw the scroll position bar + int barHeight = (r.height() - BUTTON_SIZE * 2) * pageSize / count; + barHeight = CLIP(barHeight, BUTTON_SIZE, r.height() - BUTTON_SIZE * 2); + int barY = (count <= pageSize) ? r.top + BUTTON_SIZE : r.top + BUTTON_SIZE + + (r.height() - BUTTON_SIZE * 2 - barHeight) * index / (count - pageSize); + + _surface.fillRect(Common::Rect(r.left + 2, barY + 2, r.right - 2, barY + barHeight - 3), INFO_MIDDLE); + ui.drawDialogRect(_surface, Common::Rect(r.left, barY, r.right, barY + barHeight), true); +} + +void WidgetBase::handleScrollbarEvents(int index, int pageSize, int count) { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + + // If they're dragging the scrollbar thumb, keep it selected whilst the button is being held + if ((events._pressed || events._released) && ui._scrollHighlight == SH_THUMBNAIL) + return; + + ui._scrollHighlight = SH_NONE; + + if ((!events._pressed && !events._rightReleased) || !_scroll) + return; + + Common::Rect r = getScrollBarBounds(); + r.translate(_bounds.left, _bounds.top); + + // Calculate the Scroll Position bar + int barHeight = (r.height() - BUTTON_SIZE * 2) * pageSize / count; + barHeight = CLIP(barHeight, BUTTON_SIZE, r.height() - BUTTON_SIZE * 2); + int barY = (count <= pageSize) ? r.top + BUTTON_SIZE : r.top + BUTTON_SIZE + + (r.height() - BUTTON_SIZE * 2 - barHeight) * index / (count - pageSize); + + if (Common::Rect(r.left, r.top, r.right, r.top + BUTTON_SIZE).contains(mousePos)) + // Mouse on scroll up button + ui._scrollHighlight = SH_SCROLL_UP; + else if (Common::Rect(r.left, r.top + BUTTON_SIZE, r.right, barY).contains(mousePos)) + // Mouse on paging up area (the area of the vertical bar above the thumbnail) + ui._scrollHighlight = SH_PAGE_UP; + else if (Common::Rect(r.left, barY, r.right, barY + barHeight).contains(mousePos)) + // Mouse on scrollbar thumb + ui._scrollHighlight = SH_THUMBNAIL; + else if (Common::Rect(r.left, barY + barHeight, r.right, r.bottom - BUTTON_SIZE).contains(mousePos)) + // Mouse on paging down area (the area of the vertical bar below the thumbnail) + ui._scrollHighlight = SH_PAGE_DOWN; + else if (Common::Rect(r.left, r.bottom - BUTTON_SIZE, r.right, r.bottom).contains(mousePos)) + // Mouse on scroll down button + ui._scrollHighlight = SH_SCROLL_DOWN; +} + +void WidgetBase::handleScrolling(int &scrollIndex, int pageSize, int max) { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::KeyCode keycode = ui._keyState.keycode; + Common::Point mousePos = events.mousePos(); + + Common::Rect r = getScrollBarBounds(); + r.translate(_bounds.left, _bounds.top); + + if (ui._scrollHighlight != SH_NONE || keycode == Common::KEYCODE_HOME || keycode == Common::KEYCODE_END + || keycode == Common::KEYCODE_PAGEUP || keycode == Common::KEYCODE_PAGEDOWN + || keycode == Common::KEYCODE_UP || keycode == Common::KEYCODE_DOWN) { + // Check for the scrollbar + if (ui._scrollHighlight == SH_THUMBNAIL) { + int yp = mousePos.y; + yp = CLIP(yp, r.top + BUTTON_SIZE + 3, r.bottom - BUTTON_SIZE - 3); + + // Calculate the line number that corresponds to the position that the mouse is on the scrollbar + int lineNum = (yp - r.top - BUTTON_SIZE - 3) * (max - pageSize) / (r.height() - BUTTON_SIZE * 2 - 6); + scrollIndex = CLIP(lineNum, 0, max - pageSize); + } + + // Get the current frame so we can check the scroll timer against it + uint32 frameNum = events.getFrameCounter(); + + if (frameNum > _dialogTimer) { + // Set the timeout for the next scroll if the mouse button remains held down + _dialogTimer = (_dialogTimer == 0) ? frameNum + pageSize : frameNum + 1; + + // Check for Scroll Up + if ((ui._scrollHighlight == SH_SCROLL_UP || keycode == Common::KEYCODE_UP) && scrollIndex) + --scrollIndex; + + // Check for Page Up + else if ((ui._scrollHighlight == SH_PAGE_UP || keycode == Common::KEYCODE_PAGEUP) && scrollIndex) + scrollIndex -= pageSize; + + // Check for Page Down + else if ((ui._scrollHighlight == SH_PAGE_DOWN || keycode == Common::KEYCODE_PAGEDOWN) + && (scrollIndex + pageSize < max)) { + scrollIndex += pageSize; + if (scrollIndex + pageSize >max) + scrollIndex = max - pageSize; + } + + // Check for Scroll Down + else if ((ui._scrollHighlight == SH_SCROLL_DOWN || keycode == Common::KEYCODE_DOWN) && (scrollIndex + pageSize < max)) + ++scrollIndex; + } + + if (keycode == Common::KEYCODE_END) + scrollIndex = max - pageSize; + + if (scrollIndex < 0 || keycode == Common::KEYCODE_HOME) + scrollIndex = 0; + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock |