/* 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 3 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, see . * */ #include "ultima/ultima8/gumps/gump.h" #include "ultima/ultima8/gfx/render_surface.h" #include "ultima/ultima8/gfx/shape.h" #include "ultima/ultima8/gfx/shape_frame.h" #include "ultima/ultima8/gfx/shape_archive.h" #include "ultima/ultima8/games/game_data.h" #include "ultima/ultima8/gumps/gump_notify_process.h" #include "ultima/ultima8/kernel/kernel.h" #include "ultima/ultima8/kernel/mouse.h" #include "ultima/ultima8/kernel/object_manager.h" #include "ultima/ultima8/ultima8.h" namespace Ultima { namespace Ultima8 { DEFINE_RUNTIME_CLASSTYPE_CODE(Gump) Gump::Gump() : Object(), _parent(nullptr), _owner(0), _x(0), _y(0), _flags(0), _layer(0), _index(-1), _shape(nullptr), _frameNum(0), _focusChild(nullptr), _notifier(0), _processResult(0) { } Gump::Gump(int inX, int inY, int width, int height, uint16 inOwner, uint32 inFlags, int32 inLayer) : Object(), _owner(inOwner), _parent(nullptr), _x(inX), _y(inY), _dims(0, 0, width, height), _flags(inFlags), _layer(inLayer), _index(-1), _shape(nullptr), _frameNum(0), _children(), _focusChild(nullptr), _notifier(0), _processResult(0) { assignObjId(); // gumps always get an objid } Gump::~Gump() { // Get rid of focus if (_focusChild) _focusChild->OnFocus(false); _focusChild = nullptr; // Delete all children for (auto *g : _children) { delete g; } } void Gump::InitGump(Gump *newparent, bool take_focus) { if (newparent) newparent->AddChild(this, take_focus); else Ultima8Engine::get_instance()->addGump(this); if (_owner && !_notifier) CreateNotifier(); } void Gump::SetShape(FrameID frame, bool adjustsize) { _shape = GameData::get_instance()->getShape(frame); _frameNum = frame._frameNum; if (adjustsize && _shape) { UpdateDimsFromShape(); } } void Gump::UpdateDimsFromShape() { const ShapeFrame *sf = _shape->getFrame(_frameNum); assert(sf); _dims.left = -sf->_xoff; _dims.top = -sf->_yoff; _dims.setWidth(sf->_width); _dims.setHeight(sf->_height); } void Gump::CreateNotifier() { assert(_notifier == 0); // Create us a GumpNotifyProcess GumpNotifyProcess *p = new GumpNotifyProcess(_owner); p->setGump(this); _notifier = Kernel::get_instance()->addProcess(p); } void Gump::SetNotifyProcess(GumpNotifyProcess *proc) { assert(_notifier == 0); _notifier = proc->getPid(); } GumpNotifyProcess *Gump::GetNotifyProcess() { return dynamic_cast(Kernel::get_instance()-> getProcess(_notifier)); } void Gump::Close(bool no_del) { GumpNotifyProcess *p = GetNotifyProcess(); if (p) { p->notifyClosing(_processResult); } _notifier = 0; _flags |= FLAG_CLOSING; if (!_parent) { if (!no_del) delete this; } else { _parent->ChildNotify(this, Gump::GUMP_CLOSING); if (!no_del) _flags |= FLAG_CLOSE_AND_DEL; } } void Gump::RenderSurfaceChanged() { // Iterate all children Std::list::iterator it = _children.reverse_begin(); Std::list::iterator end = _children.end(); while (it != end) { (*it)->RenderSurfaceChanged(); --it; } } void Gump::run() { // Iterate all children Std::list::iterator it = _children.begin(); Std::list::iterator end = _children.end(); while (it != end) { Gump *g = *it; // Run the child if it's not closing if (!(g->_flags & FLAG_CLOSING)) g->run(); // If closing, we can kill it if (g->_flags & FLAG_CLOSING) { it = _children.erase(it); FindNewFocusChild(); if (g->_flags & FLAG_CLOSE_AND_DEL) delete g; } else { ++it; } } } void Gump::CloseItemDependents() { // Close it, and return if (_flags & FLAG_ITEM_DEPENDENT) { Close(); return; } // Pass the MapChanged message to all the children Std::list::iterator it = _children.begin(); Std::list::iterator end = _children.end(); while (it != end) { Gump *g = *it; // Pass to child if it's not closing if (!(g->_flags & FLAG_CLOSING)) g->CloseItemDependents(); // If closing, we can kill it if (g->_flags & FLAG_CLOSING) { it = _children.erase(it); FindNewFocusChild(); if (g->_flags & FLAG_CLOSE_AND_DEL) delete g; } else { ++it; } } } bool Gump::GetMouseCursor(int32 mx, int32 my, Shape &shape, int32 &frame) { ParentToGump(mx, my); bool ret = false; // This reverse iterates the children Std::list::iterator it; for (it = _children.reverse_begin(); it != _children.end(); --it) { Gump *g = *it; // Not if closing or hidden if (g->_flags & FLAG_CLOSING || g->IsHidden()) continue; // It's got the point if (g->PointOnGump(mx, my)) ret = g->GetMouseCursor(mx, my, shape, frame); if (ret) break; } return ret; } void Gump::Paint(RenderSurface *surf, int32 lerp_factor, bool scaled) { // Don't paint if hidden if (IsHidden()) return; // Get old Origin int32 ox = 0, oy = 0; surf->GetOrigin(ox, oy); // Set the new Origin int32 nx = 0, ny = 0; GumpToParent(nx, ny); surf->SetOrigin(ox + nx, oy + ny); // Get Old Clipping Rect Common::Rect32 old_rect = surf->getClippingRect(); // Set new clipping rect Common::Rect32 new_rect(_dims); new_rect.clip(old_rect); surf->setClippingRect(new_rect); // Paint This PaintThis(surf, lerp_factor, scaled); // Paint children PaintChildren(surf, lerp_factor, scaled); // Reset The Clipping Rect surf->setClippingRect(old_rect); // Reset The Origin surf->SetOrigin(ox, oy); } void Gump::PaintThis(RenderSurface *surf, int32 /*lerp_factor*/, bool /*scaled*/) { if (_shape) surf->Paint(_shape, _frameNum, 0, 0); } void Gump::PaintChildren(RenderSurface *surf, int32 lerp_factor, bool scaled) { for (auto *g : _children) { // Paint if not closing if (!(g->_flags & FLAG_CLOSING)) g->Paint(surf, lerp_factor, scaled); } } void Gump::PaintCompositing(RenderSurface *surf, int32 lerp_factor, int32 sx, int32 sy) { // Don't paint if hidden if (IsHidden()) return; // Get old Origin int32 ox = 0, oy = 0; surf->GetOrigin(ox, oy); // FIXME - Big accuracy problems here with the origin and clipping rect // Set the new Origin surf->SetOrigin(0, 0); // Get Old Clipping Rect Common::Rect32 old_rect = surf->getClippingRect(); // Set new clipping rect Common::Rect32 new_rect(_dims); GumpRectToScreenSpace(new_rect, ROUND_OUTSIDE); new_rect.clip(old_rect); surf->setClippingRect(new_rect); // Iterate all children Std::list::iterator it = _children.reverse_begin(); Std::list::iterator end = _children.end(); while (it != end) { Gump *g = *it; // Paint if not closing if (!g->IsClosing()) g->PaintCompositing(surf, lerp_factor, sx, sy); --it; } // Paint This PaintComposited(surf, lerp_factor, sx, sy); // Reset The Clipping Rect surf->setClippingRect(old_rect); // Reset The Origin surf->SetOrigin(ox, oy); } void Gump::PaintComposited(RenderSurface * /*surf*/, int32 /*lerp_factor*/, int32 /*scalex*/, int32 /*scaley*/) { } Gump *Gump::FindGump(int mx, int my) { int32 gx = mx, gy = my; ParentToGump(gx, gy); Gump *gump = nullptr; // Iterate all children Std::list::iterator it = _children.reverse_begin(); Std::list::iterator end = _children.end(); while (it != end && !gump) { Gump *g = *it; gump = g->FindGump(gx, gy); --it; } // it's over a child if (gump) return gump; // it's over this gump if (PointOnGump(mx, my)) return this; return nullptr; } void Gump::setRelativePosition(Gump::Position pos, int xoffset, int yoffset) { if (_parent) { Common::Rect32 rect = _parent->getDims(); switch (pos) { case CENTER: Move(rect.width() / 2 - _dims.width() / 2 + xoffset, rect.height() / 2 - _dims.height() / 2 + yoffset); break; case TOP_LEFT: Move(xoffset, yoffset); break; case TOP_RIGHT: Move(rect.width() - _dims.width() + xoffset, yoffset); break; case BOTTOM_LEFT: Move(xoffset, rect.height() - _dims.height() + yoffset); break; case BOTTOM_RIGHT: Move(rect.width() - _dims.width() + xoffset, rect.height() - _dims.height() + yoffset); break; case TOP_CENTER: Move(rect.width() / 2 - _dims.width() / 2 + xoffset, yoffset); break; case BOTTOM_CENTER: Move(rect.width() / 2 - _dims.width() / 2 + xoffset, rect.height() - _dims.height() + yoffset); break; case LEFT_CENTER: Move(xoffset, rect.height() / 2 - _dims.height() / 2 + yoffset); break; case RIGHT_CENTER: Move(rect.width() - _dims.width() + xoffset, rect.height() / 2 - _dims.height() / 2 + yoffset); break; default: break; } } } bool Gump::PointOnGump(int mx, int my) { int32 gx = mx, gy = my; ParentToGump(gx, gy); // First check again rectangle if (!_dims.contains(gx, gy)) { return false; } if (!_shape) { // no shape? Then if it's in the rectangle it's on the gump. return true; } const ShapeFrame *sf = _shape->getFrame(_frameNum); assert(sf); if (sf->hasPoint(gx, gy)) { return true; } // reverse-iterate children Std::list::iterator it; for (it = _children.reverse_begin(); it != _children.end(); --it) { Gump *g = *it; // It's got the point if (g->PointOnGump(gx, gy)) return true; } return false; } // Convert a screen space point to a gump point void Gump::ScreenSpaceToGump(int32 &sx, int32 &sy, PointRoundDir r) { // This is a recursive operation. We get each // parent to convert the point into their local // coords. if (_parent) _parent->ScreenSpaceToGump(sx, sy, r); ParentToGump(sx, sy, r); } // Convert a gump point to a screen space point void Gump::GumpToScreenSpace(int32 &gx, int32 &gy, PointRoundDir r) { // This is a recursive operation. We get each // gump to convert the point to their parent GumpToParent(gx, gy, r); if (_parent) _parent->GumpToScreenSpace(gx, gy, r); } // Convert a parent relative point to a gump point void Gump::ParentToGump(int32 &px, int32 &py, PointRoundDir) { px -= _x; px += _dims.left; py -= _y; py += _dims.top; } // Convert a gump point to parent relative point void Gump::GumpToParent(int32 &gx, int32 &gy, PointRoundDir) { gx -= _dims.left; gx += _x; gy -= _dims.top; gy += _y; } // Transform a rectangle to screenspace from gumpspace void Gump::GumpRectToScreenSpace(Common::Rect32 &gr, RectRoundDir r) { PointRoundDir tl = (r == ROUND_INSIDE ? ROUND_BOTTOMRIGHT : ROUND_TOPLEFT); PointRoundDir br = (r == ROUND_OUTSIDE ? ROUND_BOTTOMRIGHT : ROUND_TOPLEFT); int32 x1 = gr.left, y1 = gr.top; int32 x2 = gr.right, y2 = gr.bottom; GumpToScreenSpace(x1, y1, tl); GumpToScreenSpace(x2, y2, br); gr.moveTo(x1, y1); if (gr.width() != 0) gr.setWidth(x2 - x1); if (gr.height() != 0) gr.setHeight(y2 - y1); } // Transform a rectangle to gumpspace from screenspace void Gump::ScreenSpaceToGumpRect(Common::Rect32 &sr, RectRoundDir r) { PointRoundDir tl = (r == ROUND_INSIDE ? ROUND_BOTTOMRIGHT : ROUND_TOPLEFT); PointRoundDir br = (r == ROUND_OUTSIDE ? ROUND_BOTTOMRIGHT : ROUND_TOPLEFT); int32 x1 = sr.left, y1 = sr.top; int32 x2 = sr.right, y2 = sr.bottom; ScreenSpaceToGump(x1, y1, tl); ScreenSpaceToGump(x2, y2, br); sr.moveTo(x1, y1); if (sr.width() != 0) sr.setWidth(x2 - x1); if (sr.height() != 0) sr.setHeight(y2 - y1); } uint16 Gump::TraceObjId(int32 mx, int32 my) { // Convert to local coords int32 gx = mx, gy = my; ParentToGump(gx, gy); uint16 objId_ = 0; // reverse-iterate children Std::list::iterator it; for (it = _children.reverse_begin(); it != _children.end(); --it) { Gump *g = *it; // Not if closing or hidden if (g->_flags & FLAG_CLOSING || g->IsHidden()) continue; // It's got the point if (g->PointOnGump(gx, gy)) objId_ = g->TraceObjId(gx, gy); if (objId_ && objId_ != 65535) break; } // if (!objId_ || objId_ == 65535) // if (PointOnGump(mx,my)) // objId_ = getObjId(); return objId_; } bool Gump::GetLocationOfItem(uint16 itemid, int32 &gx, int32 &gy, int32 lerp_factor) { gx = 0; gy = 0; return false; } // Find a child gump that matches the matching function Gump *Gump::FindGump(const FindGumpPredicate predicate, bool recursive) { if (predicate(this)) return this; for (auto *g : _children) { // Not if closing if (g->_flags & FLAG_CLOSING) continue; if (predicate(g)) return g; } if (!recursive) return nullptr; // Recursive Iterate all children for (auto *g : _children) { // Not if closing if (g->_flags & FLAG_CLOSING) continue; Gump *match = g->FindGump(predicate, recursive); if (match) return match; } return nullptr; } // Makes this gump the focus void Gump::MakeFocus() { // By default we WON'T do anything if (_parent) { if (_parent->_focusChild) _parent->_focusChild->OnFocus(false); _parent->_focusChild = this; } OnFocus(true); } void Gump::FindNewFocusChild() { if (_focusChild) _focusChild->OnFocus(false); _focusChild = nullptr; // Now add the gump to use as the new focus Std::list::iterator it = _children.reverse_begin(); if (it != _children.end()) { (*it)->MakeFocus(); } } // Adds a child to the list void Gump::AddChild(Gump *gump, bool take_focus) { if (!gump) return; // Remove it if required Gump *old_parent = gump->GetParent(); if (old_parent) old_parent->RemoveChild(gump); // Now add the gump in the correct spot Std::list::iterator it = _children.begin(); Std::list::iterator end = _children.end(); for (; it != end; ++it) { Gump *other = *it; // Why don't we check for FLAG_CLOSING here? // Because we want to make sure that the sort order is always valid // If we are same layer as focus and we won't take it, we will not be // placed in front of it if (!take_focus && other == _focusChild && other->_layer == gump->_layer) break; // Lower layers get placed before higher layers if (other->_layer > gump->_layer) break; } // Now add it _children.insert(it, gump); gump->_parent = this; // Make the gump the focus if needed if (take_focus || !_focusChild) { if (_focusChild) _focusChild->OnFocus(false); gump->OnFocus(true); _focusChild = gump; } } // Remove a gump from the list void Gump::RemoveChild(Gump *gump) { if (!gump) return; // Remove it _children.remove(gump); gump->_parent = nullptr; // Remove focus, the give upper most gump the focus if (gump == _focusChild) { FindNewFocusChild(); } } void Gump::MoveChildToFront(Gump *gump) { if (!gump) return; _children.remove(gump); Std::list::iterator it = _children.begin(); Std::list::iterator end = _children.end(); for (; it != end; ++it) { Gump *other = *it; // Lower layers get placed before higher layers if (other->_layer > gump->_layer) break; } _children.insert(it, gump); } Gump *Gump::GetRootGump() { if (!_parent) return this; return _parent->GetRootGump(); } bool Gump::onDragStart(int32 mx, int32 my) { if (IsDraggable() && _parent) { ParentToGump(mx, my); Mouse::get_instance()->setDraggingOffset(mx, my); _parent->MoveChildToFront(this); return true; } return false; } void Gump::onDragStop(int32 mx, int32 my) { } void Gump::onDrag(int32 mx, int32 my) { int32 dx, dy; Mouse::get_instance()->getDraggingOffset(dx, dy); Move(mx - dx, my - dy); } // // Input handling // Gump *Gump::onMouseDown(int button, int32 mx, int32 my) { // Convert to local coords ParentToGump(mx, my); Gump *handled = nullptr; // Iterate children backwards Std::list::iterator it; for (it = _children.reverse_begin(); it != _children.end(); --it) { Gump *g = *it; // Not if closing or hidden if (g->_flags & FLAG_CLOSING || g->IsHidden()) continue; // It's got the point if (g->PointOnGump(mx, my)) handled = g->onMouseDown(button, mx, my); if (handled) break; } return handled; } Gump *Gump::onMouseMotion(int32 mx, int32 my) { // Convert to local coords ParentToGump(mx, my); Gump *handled = nullptr; // Iterate children backwards Std::list::iterator it; for (it = _children.reverse_begin(); it != _children.end(); --it) { Gump *g = *it; // Not if closing or hidden if (g->_flags & FLAG_CLOSING || g->IsHidden()) continue; // It's got the point if (g->PointOnGump(mx, my)) handled = g->onMouseMotion(mx, my); if (handled) break; } // All gumps need to handle mouse motion if (!handled) handled = this; return handled; } // // KeyInput // bool Gump::OnKeyDown(int key, int mod) { bool handled = false; if (_focusChild) handled = _focusChild->OnKeyDown(key, mod); return handled; } bool Gump::OnKeyUp(int key) { bool handled = false; if (_focusChild) handled = _focusChild->OnKeyUp(key); return handled; } bool Gump::OnTextInput(int unicode) { bool handled = false; if (_focusChild) handled = _focusChild->OnTextInput(unicode); return handled; } bool Gump::mustSave(bool toplevel) const { // DONT_SAVE flag if (_flags & FLAG_DONT_SAVE) return false; // don't save when ready for deletion if (_flags & FLAG_CLOSE_AND_DEL) return false; if (toplevel) { // don't save gumps with parents, unless parent is a core gump if (_parent && !(_parent->_flags & FLAG_CORE_GUMP)) return false; } return true; } void Gump::saveData(Common::WriteStream *ws) { Object::saveData(ws); ws->writeUint16LE(_owner); ws->writeUint32LE(static_cast(_x)); ws->writeUint32LE(static_cast(_y)); ws->writeUint32LE(static_cast(_dims.left)); ws->writeUint32LE(static_cast(_dims.top)); ws->writeUint32LE(static_cast(_dims.width())); ws->writeUint32LE(static_cast(_dims.height())); ws->writeUint32LE(_flags); ws->writeUint32LE(static_cast(_layer)); ws->writeUint32LE(static_cast(_index)); uint16 flex = 0; uint32 shapenum = 0; if (_shape) { _shape->getShapeId(flex, shapenum); } ws->writeUint16LE(flex); ws->writeUint32LE(shapenum); ws->writeUint32LE(_frameNum); if (_focusChild) ws->writeUint16LE(_focusChild->getObjId()); else ws->writeUint16LE(0); ws->writeUint16LE(_notifier); ws->writeUint32LE(_processResult); unsigned int childcount = 0; for (auto *g : _children) { if (!g->mustSave(false)) continue; childcount++; } // write children: ws->writeUint32LE(childcount); for (auto *g : _children) { if (!g->mustSave(false)) continue; ObjectManager::get_instance()->saveObject(ws, g); } } bool Gump::loadData(Common::ReadStream *rs, uint32 version) { if (!Object::loadData(rs, version)) return false; _owner = rs->readUint16LE(); _x = static_cast(rs->readUint32LE()); _y = static_cast(rs->readUint32LE()); int dx = static_cast(rs->readUint32LE()); int dy = static_cast(rs->readUint32LE()); int dw = static_cast(rs->readUint32LE()); int dh = static_cast(rs->readUint32LE()); _dims.moveTo(dx, dy); _dims.setWidth(dw); _dims.setHeight(dh); _flags = rs->readUint32LE(); _layer = static_cast(rs->readUint32LE()); _index = static_cast(rs->readUint32LE()); _shape = nullptr; ShapeArchive *flex = GameData::get_instance()->getShapeFlex(rs->readUint16LE()); uint32 shapenum = rs->readUint32LE(); if (flex) { _shape = flex->getShape(shapenum); if (shapenum > 0 && !_shape) { warning("Gump shape %d is not valid. Corrupt save?", shapenum); return false; } } _frameNum = rs->readUint32LE(); uint16 focusid = rs->readUint16LE(); _focusChild = nullptr; _notifier = rs->readUint16LE(); _processResult = rs->readUint32LE(); // read children uint32 childcount = rs->readUint32LE(); if (childcount > 65535) { warning("Improbable gump child count %d. Corrupt save?", childcount); return false; } for (unsigned int i = 0; i < childcount; ++i) { Object *obj = ObjectManager::get_instance()->loadObject(rs, version); Gump *child = dynamic_cast(obj); if (!child) return false; AddChild(child, false); if (child->getObjId() == focusid) _focusChild = child; } return true; } } // End of namespace Ultima8 } // End of namespace Ultima