From 1337cd3dec86d64476bc4248e8368848d70b56e5 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sat, 5 Mar 2016 23:56:38 -0600 Subject: SCI32: Implement kEditText --- engines/sci/engine/kgraphics32.cpp | 9 +- engines/sci/engine/selector.cpp | 6 + engines/sci/engine/selector.h | 4 +- engines/sci/event.cpp | 38 ++- engines/sci/event.h | 16 +- engines/sci/graphics/controls32.cpp | 435 ++++++++++++++++++++++----------- engines/sci/graphics/controls32.h | 82 ++++++- engines/sci/graphics/frameout.cpp | 19 +- engines/sci/graphics/frameout.h | 33 ++- engines/sci/graphics/plane32.cpp | 4 +- engines/sci/graphics/plane32.h | 25 +- engines/sci/graphics/screen_item32.cpp | 4 +- engines/sci/graphics/screen_item32.h | 2 +- engines/sci/graphics/text32.cpp | 69 +++++- engines/sci/graphics/text32.h | 82 +++++-- engines/sci/sci.cpp | 2 +- 16 files changed, 610 insertions(+), 220 deletions(-) (limited to 'engines') diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index 0463b125ae..8d8b96167c 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -670,14 +670,9 @@ reg_t kBitmapCreateFromUnknown(EngineState *s, int argc, reg_t *argv) { // but it handles events on its own, using an internal loop, instead of using SCI // scripts for event management like kEditControl does. Called by script 64914, // DEdit::hilite(). -reg_t kEditText(EngineState *s, int argc, reg_t *argv) { - reg_t controlObject = argv[0]; - - if (!controlObject.isNull()) { - g_sci->_gfxControls32->kernelTexteditChange(controlObject); - } - return s->r_acc; +reg_t kEditText(EngineState *s, int argc, reg_t *argv) { + return g_sci->_gfxControls32->kernelEditText(argv[0]); } reg_t kAddLine(EngineState *s, int argc, reg_t *argv) { diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index 1393e96880..ac621f58ae 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -180,6 +180,7 @@ void Kernel::mapSelectors() { FIND_SELECTOR(back); FIND_SELECTOR(skip); FIND_SELECTOR(borderColor); + FIND_SELECTOR(width); FIND_SELECTOR(fixPriority); FIND_SELECTOR(mirrored); FIND_SELECTOR(visible); @@ -192,7 +193,12 @@ void Kernel::mapSelectors() { FIND_SELECTOR(textLeft); FIND_SELECTOR(textBottom); FIND_SELECTOR(textRight); + FIND_SELECTOR(title); + FIND_SELECTOR(titleFont); + FIND_SELECTOR(titleFore); + FIND_SELECTOR(titleBack); FIND_SELECTOR(magnifier); + FIND_SELECTOR(frameOut); FIND_SELECTOR(casts); #endif } diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index a8bbbe75e3..f2d06d1cf4 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -147,6 +147,7 @@ struct SelectorCache { Selector skip; Selector dimmed; Selector borderColor; + Selector width; Selector fixPriority; Selector mirrored; @@ -155,9 +156,10 @@ struct SelectorCache { Selector useInsetRect; Selector inTop, inLeft, inBottom, inRight; Selector textTop, textLeft, textBottom, textRight; + Selector title, titleFont, titleFore, titleBack; Selector magnifier; - + Selector frameOut; Selector casts; // needed for sync'ing screen items/planes with scripts, when our save/restore code is patched in (see GfxFrameout::syncWithScripts) #endif }; diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp index 3322a273ae..34f1618514 100644 --- a/engines/sci/event.cpp +++ b/engines/sci/event.cpp @@ -29,6 +29,9 @@ #include "sci/console.h" #include "sci/engine/state.h" #include "sci/engine/kernel.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/frameout.h" +#endif #include "sci/graphics/screen.h" namespace Sci { @@ -133,8 +136,13 @@ static int altify(int ch) { } SciEvent EventManager::getScummVMEvent() { - SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) }; - SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) }; +#ifdef ENABLE_SCI32 + SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() }; + SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() }; +#else + SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point() }; + SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point() }; +#endif Common::EventManager *em = g_system->getEventManager(); Common::Event ev; @@ -155,7 +163,20 @@ SciEvent EventManager::getScummVMEvent() { // via pollEvent. // We also adjust the position based on the scaling of the screen. Common::Point mousePos = em->getMousePos(); - g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x); + +#if ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + Buffer &screen = g_sci->_gfxFrameout->getCurrentBuffer(); + + Common::Point mousePosSci = mousePos; + mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight)); + noEvent.mousePosSci = input.mousePosSci = mousePosSci; + } else { +#endif + g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x); +#if ENABLE_SCI32 + } +#endif noEvent.mousePos = input.mousePos = mousePos; @@ -302,6 +323,11 @@ SciEvent EventManager::getScummVMEvent() { input.character = altify(input.character); if (getSciVersion() <= SCI_VERSION_1_MIDDLE && (scummVMKeyFlags & Common::KBD_CTRL) && input.character > 0 && input.character < 27) input.character += 96; // 0x01 -> 'a' +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2 && (scummVMKeyFlags & Common::KBD_CTRL) && input.character == 'c') { + input.character = SCI_KEY_ETX; + } +#endif // If no actual key was pressed (e.g. if only a modifier key was pressed), // ignore the event @@ -329,7 +355,11 @@ void EventManager::updateScreen() { } SciEvent EventManager::getSciEvent(uint32 mask) { - SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) }; +#ifdef ENABLE_SCI32 + SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() }; +#else + SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point() }; +#endif EventManager::updateScreen(); diff --git a/engines/sci/event.h b/engines/sci/event.h index 0d0c550622..15a94b3e73 100644 --- a/engines/sci/event.h +++ b/engines/sci/event.h @@ -39,11 +39,18 @@ struct SciEvent { uint16 character; /** - * The mouse position at the time the event was created. - * - * These are display coordinates! + * The mouse position at the time the event was created, + * in display coordinates. */ Common::Point mousePos; + +#ifdef ENABLE_SCI32 + /** + * The mouse position at the time the event was created, + * in script coordinates. + */ + Common::Point mousePosSci; +#endif }; /*Values for type*/ @@ -59,6 +66,9 @@ struct SciEvent { #define SCI_EVENT_ANY 0x7fff /* Keycodes of special keys: */ +#ifdef ENABLE_SCI32 +#define SCI_KEY_ETX 3 +#endif #define SCI_KEY_ESC 27 #define SCI_KEY_BACKSPACE 8 #define SCI_KEY_ENTER 13 diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp index df518888a9..fc028f7584 100644 --- a/engines/sci/graphics/controls32.cpp +++ b/engines/sci/graphics/controls32.cpp @@ -23,9 +23,11 @@ #include "common/system.h" #include "sci/sci.h" +#include "sci/console.h" #include "sci/event.h" #include "sci/engine/kernel.h" #include "sci/engine/seg_manager.h" +#include "sci/engine/state.h" #include "sci/graphics/cache.h" #include "sci/graphics/compare.h" #include "sci/graphics/controls32.h" @@ -34,174 +36,323 @@ #include "sci/graphics/text32.h" namespace Sci { +GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text) : + _segMan(segMan), + _gfxCache(cache), + _gfxText32(text), + _overwriteMode(false), + _nextCursorFlashTick(0) {} -GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text) - : _segMan(segMan), _cache(cache), _text(text) { -} +GfxControls32::~GfxControls32() {} -GfxControls32::~GfxControls32() { -} +reg_t GfxControls32::kernelEditText(const reg_t controlObject) { + SegManager *segMan = _segMan; -void GfxControls32::kernelTexteditChange(reg_t controlObject) { - SciEvent curEvent; - uint16 maxChars = 40; //readSelectorValue(_segMan, controlObject, SELECTOR(max)); // TODO - reg_t textReference = readSelector(_segMan, controlObject, SELECTOR(text)); - GfxFont *font = _cache->getFont(readSelectorValue(_segMan, controlObject, SELECTOR(font))); - Common::String text; - uint16 textSize; - bool textChanged = false; - bool textAddChar = false; - Common::Rect rect; + TextEditor editor; + reg_t textObject = readSelector(_segMan, controlObject, SELECTOR(text)); + editor.text = _segMan->getString(textObject); + editor.foreColor = readSelectorValue(_segMan, controlObject, SELECTOR(fore)); + editor.backColor = readSelectorValue(_segMan, controlObject, SELECTOR(back)); + editor.skipColor = readSelectorValue(_segMan, controlObject, SELECTOR(skip)); + editor.fontId = readSelectorValue(_segMan, controlObject, SELECTOR(font)); + editor.maxLength = readSelectorValue(_segMan, controlObject, SELECTOR(width)); + editor.bitmap = readSelector(_segMan, controlObject, SELECTOR(bitmap)); + editor.cursorCharPosition = 0; + editor.cursorIsDrawn = false; + editor.borderColor = readSelectorValue(_segMan, controlObject, SELECTOR(borderColor)); - if (textReference.isNull()) - error("kEditControl called on object that doesn't have a text reference"); - text = _segMan->getString(textReference); + reg_t titleObject = readSelector(_segMan, controlObject, SELECTOR(title)); + + int16 titleHeight = 0; + GuiResourceId titleFontId = readSelectorValue(_segMan, controlObject, SELECTOR(titleFont)); + if (!titleObject.isNull()) { + GfxFont *titleFont = _gfxCache->getFont(titleFontId); + titleHeight += _gfxText32->scaleUpHeight(titleFont->getHeight()) + 1; + if (editor.borderColor != -1) { + titleHeight += 2; + } + } - // TODO: Finish this - warning("kEditText ('%s')", text.c_str()); - return; + int16 width = 0; + int16 height = titleHeight; - uint16 cursorPos = 0; - //uint16 oldCursorPos = cursorPos; - bool captureEvents = true; - EventManager* eventMan = g_sci->getEventManager(); + GfxFont *editorFont = _gfxCache->getFont(editor.fontId); + height += _gfxText32->scaleUpHeight(editorFont->getHeight()) + 1; + _gfxText32->setFont(editor.fontId); + int16 emSize = _gfxText32->getCharWidth('M', true); + width += editor.maxLength * emSize + 1; + if (editor.borderColor != -1) { + width += 4; + height += 2; + } - while (captureEvents) { - curEvent = g_sci->getEventManager()->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK); + Common::Rect editorPlaneRect(width, height); + editorPlaneRect.translate(readSelectorValue(_segMan, controlObject, SELECTOR(x)), readSelectorValue(_segMan, controlObject, SELECTOR(y))); - if (curEvent.type == SCI_EVENT_NONE) { - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event + reg_t planeObj = readSelector(_segMan, controlObject, SELECTOR(plane)); + Plane *sourcePlane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj); + if (sourcePlane == nullptr) { + error("Could not find plane %04x:%04x", PRINT_REG(planeObj)); + } + editorPlaneRect.translate(sourcePlane->_gameRect.left, sourcePlane->_gameRect.top); + + editor.textRect = Common::Rect(2, titleHeight + 2, width - 1, height - 1); + editor.width = width; + + if (editor.bitmap.isNull()) { + reg_t out; + TextAlign alignment = (TextAlign)readSelectorValue(_segMan, controlObject, SELECTOR(mode)); + + if (titleObject.isNull()) { + bool dimmed = readSelectorValue(_segMan, controlObject, SELECTOR(dimmed)); + editor.bitmap = _gfxText32->createFontBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, dimmed, true, &out); } else { - textSize = text.size(); + Common::String title = _segMan->getString(titleObject); + int16 titleBackColor = readSelectorValue(_segMan, controlObject, SELECTOR(titleBack)); + int16 titleForeColor = readSelectorValue(_segMan, controlObject, SELECTOR(titleFore)); + editor.bitmap = _gfxText32->createTitledBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, title, titleForeColor, titleBackColor, titleFontId, true, &out); + } + } - switch (curEvent.type) { - case SCI_EVENT_MOUSE_PRESS: - // TODO: Implement mouse support for cursor change + drawCursor(editor); + + Plane *plane = new Plane(editorPlaneRect, kPlanePicTransparent); + plane->changePic(); + g_sci->_gfxFrameout->addPlane(*plane); + + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = editor.bitmap; + + ScreenItem *screenItem = new ScreenItem(plane->_object, celInfo, Common::Point(), ScaleInfo()); + plane->_screenItemList.add(screenItem); + + // frameOut must be called after the screen item is + // created, and before it is updated at the end of the + // event loop, otherwise it has both created and updated + // flags set which crashes the engine (it runs updates + // before creations) + g_sci->_gfxFrameout->frameOut(true); + + EventManager *eventManager = g_sci->getEventManager(); + bool clearTextOnInput = true; + bool textChanged = false; + for (;;) { + // We peek here because the last event needs to be allowed to + // dispatch a second time to the normal event handling system. + // In the actual engine, the event is always consumed and then + // the last event just gets posted back to the event manager for + // reprocessing, but instead, we only remove the event from the + // queue *after* we have determined it is not a defocusing event + const SciEvent event = eventManager->getSciEvent(SCI_EVENT_ANY | SCI_EVENT_PEEK); + + bool focused = true; + // Original engine did not have a QUIT event but we have to handle it + if (event.type == SCI_EVENT_QUIT) { + return NULL_REG; + } else if (event.type == SCI_EVENT_MOUSE_PRESS && !editorPlaneRect.contains(event.mousePosSci)) { + focused = false; + } else if (event.type == SCI_EVENT_KEYBOARD) { + switch (event.character) { + case SCI_KEY_ESC: + case SCI_KEY_UP: + case SCI_KEY_DOWN: + case SCI_KEY_TAB: + case SCI_KEY_SHIFT_TAB: + case SCI_KEY_ENTER: + focused = false; break; - case SCI_EVENT_KEYBOARD: - switch (curEvent.character) { - case SCI_KEY_BACKSPACE: - if (cursorPos > 0) { - cursorPos--; text.deleteChar(cursorPos); - textChanged = true; - } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_DELETE: - if (cursorPos < textSize) { - text.deleteChar(cursorPos); - textChanged = true; - } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_HOME: // HOME - cursorPos = 0; textChanged = true; - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_END: // END - cursorPos = textSize; textChanged = true; - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_LEFT: // LEFT - if (cursorPos > 0) { - cursorPos--; textChanged = true; - } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_RIGHT: // RIGHT - if (cursorPos + 1 <= textSize) { - cursorPos++; textChanged = true; - } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case 3: // returned in SCI1 late and newer when Control - C is pressed - if (curEvent.modifiers & SCI_KEYMOD_CTRL) { - // Control-C erases the whole line - cursorPos = 0; text.clear(); - textChanged = true; + } + } + + if (!focused) { + break; + } + + // Consume the event now that we know it is not one of the + // defocusing events above + eventManager->getSciEvent(SCI_EVENT_ANY); + + // NOTE: In the original engine, the font and bitmap were + // reset here on each iteration through the loop, but it + // doesn't seem like this should be necessary since + // control is not yielded back to the VM until input is + // received, which means there is nothing that could modify + // the GfxText32's state with a different font in the + // meantime + + bool shouldDeleteChar = false; + bool shouldRedrawText = false; + uint16 lastCursorPosition = editor.cursorCharPosition; + if (event.type == SCI_EVENT_KEYBOARD) { + switch (event.character) { + case SCI_KEY_LEFT: + clearTextOnInput = false; + if (editor.cursorCharPosition > 0) { + --editor.cursorCharPosition; + } + break; + + case SCI_KEY_RIGHT: + clearTextOnInput = false; + if (editor.cursorCharPosition < editor.text.size()) { + ++editor.cursorCharPosition; + } + break; + + case SCI_KEY_HOME: + clearTextOnInput = false; + editor.cursorCharPosition = 0; + break; + + case SCI_KEY_END: + clearTextOnInput = false; + editor.cursorCharPosition = editor.text.size(); + break; + + case SCI_KEY_INSERT: + clearTextOnInput = false; + // Redrawing also changes the cursor rect to + // reflect the new insertion mode + shouldRedrawText = true; + _overwriteMode = !_overwriteMode; + break; + + case SCI_KEY_DELETE: + clearTextOnInput = false; + if (editor.cursorCharPosition < editor.text.size()) { + shouldDeleteChar = true; + } + break; + + case SCI_KEY_BACKSPACE: + clearTextOnInput = false; + shouldDeleteChar = true; + if (editor.cursorCharPosition > 0) { + --editor.cursorCharPosition; + } + break; + + case SCI_KEY_ETX: + editor.text.clear(); + editor.cursorCharPosition = 0; + shouldRedrawText = true; + break; + + default: { + if (event.character >= 20 && event.character < 257) { + if (clearTextOnInput) { + clearTextOnInput = false; + editor.text.clear(); } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_UP: - case SCI_KEY_DOWN: - case SCI_KEY_ENTER: - case SCI_KEY_ESC: - case SCI_KEY_TAB: - case SCI_KEY_SHIFT_TAB: - captureEvents = false; - break; - default: - if ((curEvent.modifiers & SCI_KEYMOD_CTRL) && curEvent.character == 'c') { - // Control-C in earlier SCI games (SCI0 - SCI1 middle) - // Control-C erases the whole line - cursorPos = 0; text.clear(); - textChanged = true; - } else if (curEvent.character > 31 && curEvent.character < 256 && textSize < maxChars) { - // insert pressed character - textAddChar = true; - textChanged = true; + + if ( + (_overwriteMode && editor.cursorCharPosition < editor.maxLength) || + (editor.text.size() < editor.maxLength && _gfxText32->getCharWidth(event.character, true) + _gfxText32->getStringWidth(editor.text) < editor.textRect.width()) + ) { + if (_overwriteMode && editor.cursorCharPosition < editor.text.size()) { + editor.text.setChar(event.character, editor.cursorCharPosition); + } else { + editor.text.insertChar(event.character, editor.cursorCharPosition); + } + + ++editor.cursorCharPosition; + shouldRedrawText = true; } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; } - break; + } } } - if (textChanged) { - rect = g_sci->_gfxCompare->getNSRect(controlObject); + if (shouldDeleteChar) { + shouldRedrawText = true; + if (editor.cursorCharPosition < editor.text.size()) { + editor.text.deleteChar(editor.cursorCharPosition); + } + } - if (textAddChar) { - const char *textPtr = text.c_str(); + if (shouldRedrawText) { + eraseCursor(editor); + _gfxText32->erase(editor.textRect, true); + _gfxText32->drawTextBox(editor.text); + drawCursor(editor); + textChanged = true; + screenItem->_updated = g_sci->_gfxFrameout->getScreenCount(); + } else if (editor.cursorCharPosition != lastCursorPosition) { + eraseCursor(editor); + drawCursor(editor); + screenItem->_updated = g_sci->_gfxFrameout->getScreenCount(); + } else { + flashCursor(editor); + screenItem->_updated = g_sci->_gfxFrameout->getScreenCount(); + } - // We check if we are really able to add the new char - uint16 textWidth = 0; - while (*textPtr) - textWidth += font->getCharWidth((byte)*textPtr++); - textWidth += font->getCharWidth(curEvent.character); + g_sci->_gfxFrameout->frameOut(true); + g_sci->getSciDebugger()->onFrame(); + g_sci->getEngineState()->speedThrottler(16); + g_sci->getEngineState()->_throttleTrigger = true; + } - // Does it fit? - if (textWidth >= rect.width()) { - return; - } + g_sci->_gfxFrameout->deletePlane(*plane); + if (readSelectorValue(segMan, controlObject, SELECTOR(frameOut))) { + g_sci->_gfxFrameout->frameOut(true); + } - text.insertChar(curEvent.character, cursorPos++); + _segMan->freeHunkEntry(editor.bitmap); - // Note: the following checkAltInput call might make the text - // too wide to fit, but SSCI fails to check that too. - } + if (textChanged) { + editor.text.trim(); + SciString *string = _segMan->lookupString(textObject); + string->fromString(editor.text); + } - reg_t hunkId = readSelector(_segMan, controlObject, SELECTOR(bitmap)); - Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(controlObject); - //texteditCursorErase(); // TODO: Cursor - - // Write back string - _segMan->strcpy(textReference, text.c_str()); - // Modify the buffer and show it - warning("kernelTexteditChange"); -#if 0 - _text->createTextBitmap(controlObject, 0, 0, hunkId); - - _text->drawTextBitmap(0, 0, nsRect, controlObject); - //texteditCursorDraw(rect, text.c_str(), cursorPos); // TODO: Cursor - g_system->updateScreen(); -#endif + return make_reg(0, textChanged); +} + +void GfxControls32::drawCursor(TextEditor &editor) { + if (!editor.cursorIsDrawn) { + editor.cursorRect.left = editor.textRect.left + _gfxText32->getTextWidth(editor.text, 0, editor.cursorCharPosition); + + const int16 scaledFontHeight = _gfxText32->scaleUpHeight(_gfxText32->_font->getHeight()); + + // NOTE: The original code branched on borderColor here but + // the two branches appeared to be identical, differing only + // because the compiler decided to be differently clever + // when optimising multiplication in each branch + if (_overwriteMode) { + editor.cursorRect.top = editor.textRect.top; + editor.cursorRect.setHeight(scaledFontHeight); } else { - // TODO: Cursor - /* - if (g_system->getMillis() >= _texteditBlinkTime) { - _paint16->invertRect(_texteditCursorRect); - _paint16->bitsShow(_texteditCursorRect); - _texteditCursorVisible = !_texteditCursorVisible; - texteditSetBlinkTime(); - } - */ + editor.cursorRect.top = editor.textRect.top + scaledFontHeight - 1; + editor.cursorRect.setHeight(1); } - textAddChar = false; - textChanged = false; - g_sci->sleep(10); - } // while + const char currentChar = editor.cursorCharPosition < editor.text.size() ? editor.text[editor.cursorCharPosition] : ' '; + editor.cursorRect.setWidth(_gfxText32->getCharWidth(currentChar, true)); + + _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true); + + editor.cursorIsDrawn = true; + } + + _nextCursorFlashTick = g_sci->getTickCount() + 30; +} + +void GfxControls32::eraseCursor(TextEditor &editor) { + if (editor.cursorIsDrawn) { + _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true); + editor.cursorIsDrawn = false; + } + + _nextCursorFlashTick = g_sci->getTickCount() + 30; } +void GfxControls32::flashCursor(TextEditor &editor) { + if (g_sci->getTickCount() > _nextCursorFlashTick) { + _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true); + + editor.cursorIsDrawn = !editor.cursorIsDrawn; + _nextCursorFlashTick = g_sci->getTickCount() + 30; + } +} } // End of namespace Sci diff --git a/engines/sci/graphics/controls32.h b/engines/sci/graphics/controls32.h index 5af7c20f16..0dc3b9ce6f 100644 --- a/engines/sci/graphics/controls32.h +++ b/engines/sci/graphics/controls32.h @@ -29,6 +29,76 @@ class GfxCache; class GfxScreen; class GfxText32; +struct TextEditor { + /** + * The bitmap where the editor is rendered. + */ + reg_t bitmap; + + /** + * The width of the editor, in bitmap pixels. + */ + int16 width; + + /** + * The text in the editor. + */ + Common::String text; + + /** + * The rect where text should be drawn into the editor, + * in bitmap pixels. + */ + Common::Rect textRect; + + /** + * The color of the border. -1 indicates no border. + */ + int16 borderColor; + + /** + * The text color. + */ + uint8 foreColor; + + /** + * The background color. + */ + uint8 backColor; + + /** + * The transparent color. + */ + uint8 skipColor; + + /** + * The font used to render the text in the editor. + */ + GuiResourceId fontId; + + /** + * The current position of the cursor within the editor. + */ + uint16 cursorCharPosition; + + /** + * Whether or not the cursor is currently drawn to the + * screen. + */ + bool cursorIsDrawn; + + /** + * The rectangle for drawing the input cursor, in bitmap + * pixels. + */ + Common::Rect cursorRect; + + /** + * The maximum allowed text length, in characters. + */ + uint16 maxLength; +}; + /** * Controls class, handles drawing of controls in SCI32 (SCI2, SCI2.1, SCI3) games */ @@ -37,12 +107,18 @@ public: GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text); ~GfxControls32(); - void kernelTexteditChange(reg_t controlObject); + reg_t kernelEditText(const reg_t controlObject); private: SegManager *_segMan; - GfxCache *_cache; - GfxText32 *_text; + GfxCache *_gfxCache; + GfxText32 *_gfxText32; + + bool _overwriteMode; + uint32 _nextCursorFlashTick; + void drawCursor(TextEditor &editor); + void eraseCursor(TextEditor &editor); + void flashCursor(TextEditor &editor); }; } // End of namespace Sci diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index ab2ed7a4a5..55f5b36f56 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -365,6 +365,21 @@ void GfxFrameout::kernelDeletePlane(const reg_t object) { } } +void GfxFrameout::deletePlane(Plane &planeToFind) { + Plane *plane = _planes.findByObject(planeToFind._object); + if (plane == nullptr) { + error("Invalid plane passed to deletePlane"); + } + + if (plane->_created) { + _planes.erase(plane); + } else { + plane->_created = 0; + plane->_moved = 0; + plane->_deleted = getScreenCount(); + } +} + int16 GfxFrameout::kernelGetHighPlanePri() { return _planes.getTopSciPlanePriority(); } @@ -765,7 +780,7 @@ void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) { mergeToShowList(drawItem.rect, _showList, _overdrawThreshold); ScreenItem &screenItem = *drawItem.screenItem; // TODO: Remove -// debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), drawItem.rect.left, drawItem.rect.top, drawItem.rect.right, drawItem.rect.bottom); +// debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), PRINT_RECT(drawItem.rect)); CelObj &celObj = *screenItem._celObj; celObj.draw(_currentBuffer, screenItem, drawItem.rect, screenItem._mirrorX ^ celObj._mirrorX); } @@ -1010,8 +1025,6 @@ void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, co // NOTE: This is currBuffer->ptr in SCI engine byte *pixels = (byte *)_currentBuffer.getPixels(); - // TODO: Guessing that display width/height is the correct - // equivalent to screen width/height in SCI engine for (int pixelIndex = 0, numPixels = _currentBuffer.screenWidth * _currentBuffer.screenHeight; pixelIndex < numPixels; ++pixelIndex) { byte currentValue = pixels[pixelIndex]; int8 styleRangeValue = styleRanges[currentValue]; diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index f172997704..738011a84f 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -258,6 +258,13 @@ private: */ PlaneList _planes; + /** + * Updates an existing plane with properties from the + * given VM object. + */ + void updatePlane(Plane &plane); + +public: /** * Creates and adds a new plane to the plane list, or * cancels deletion and updates an already-existing @@ -270,15 +277,19 @@ private: void addPlane(Plane &plane); /** - * Updates an existing plane with properties from the - * given VM object. + * Deletes a plane within the current plane list. + * + * @note This method is on Screen in SCI engine, but it + * is only ever called on `GraphicsMgr.screen`. */ - void updatePlane(Plane &plane); + void deletePlane(Plane &plane); -public: const PlaneList &getPlanes() const { return _planes; } + const PlaneList &getVisiblePlanes() const { + return _visiblePlanes; + } void kernelAddPlane(const reg_t object); void kernelUpdatePlane(const reg_t object); void kernelDeletePlane(const reg_t object); @@ -422,13 +433,6 @@ private: */ void drawScreenItemList(const DrawList &screenItemList); - /** - * Updates the internal screen buffer for the next - * frame. If `shouldShowBits` is true, also sends the - * buffer to hardware. - */ - void frameOut(const bool shouldShowBits, const Common::Rect &rect = Common::Rect()); - /** * Adds a new rectangle to the list of regions to write * out to the hardware. The provided rect may be merged @@ -468,6 +472,13 @@ public: void kernelFrameOut(const bool showBits); + /** + * Updates the internal screen buffer for the next + * frame. If `shouldShowBits` is true, also sends the + * buffer to hardware. + */ + void frameOut(const bool shouldShowBits, const Common::Rect &rect = Common::Rect()); + /** * Modifies the raw pixel data for the next frame with * new palette indexes based on matched style ranges. diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp index e23017f21e..7d1487bd7c 100644 --- a/engines/sci/graphics/plane32.cpp +++ b/engines/sci/graphics/plane32.cpp @@ -43,10 +43,10 @@ void DrawList::add(ScreenItem *screenItem, const Common::Rect &rect) { #pragma mark Plane uint16 Plane::_nextObjectId = 20000; -Plane::Plane(const Common::Rect &gameRect) : +Plane::Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId) : _width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), _height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), -_pictureId(kPlanePicColored), +_pictureId(pictureId), _mirrored(false), _back(0), _priorityChanged(0), diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h index be6f71464a..65df19d924 100644 --- a/engines/sci/graphics/plane32.h +++ b/engines/sci/graphics/plane32.h @@ -133,7 +133,7 @@ private: * synchronised to another plane (which calls * changePic). */ - bool _pictureChanged; // ? + bool _pictureChanged; // TODO: Are these ever actually used? int _field_34, _field_38; // probably a point or ratio @@ -241,10 +241,18 @@ public: */ static void init(); - Plane(const Common::Rect &gameRect); + // NOTE: This constructor signature originally did not accept a + // picture ID, but some calls to construct planes with this signature + // immediately set the picture ID and then called setType again, so + // it made more sense to just make the picture ID a parameter instead. + Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId = kPlanePicColored); + Plane(const reg_t object); + Plane(const Plane &other); + void operator=(const Plane &other); + inline bool operator<(const Plane &other) const { // TODO: In SCI engine, _object is actually a uint16 and can either // contain a MemID (a handle to MemoryMgr, similar to reg_t) or @@ -317,12 +325,6 @@ private: */ inline void addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX); - /** - * If the plane is a picture plane, re-adds all cels - * from its picture resource to the plane. - */ - void changePic(); - /** * Marks all screen items to be deleted that are within * this plane and match the given picture ID. @@ -352,6 +354,13 @@ public: */ void addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX); + /** + * If the plane is a picture plane, re-adds all cels + * from its picture resource to the plane. Otherwise, + * just clears the _pictureChanged flag. + */ + void changePic(); + #pragma mark - #pragma mark Plane - Rendering private: diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp index a07dacee83..c8c9cbea0f 100644 --- a/engines/sci/graphics/screen_item32.cpp +++ b/engines/sci/graphics/screen_item32.cpp @@ -83,7 +83,7 @@ _mirrorX(false) { } } -ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect, const ScaleInfo &scaleInfo) : +ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo) : _plane(plane), _scale(scaleInfo), _useInsetRect(false), @@ -91,7 +91,7 @@ _z(0), _celInfo(celInfo), _celObj(nullptr), _fixPriority(false), -_position(rect.left, rect.top), +_position(position), _object(make_reg(0, _nextObjectId++)), _pictureId(-1), _created(g_sci->_gfxFrameout->getScreenCount()), diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h index 0ca840a13a..e054cf32f3 100644 --- a/engines/sci/graphics/screen_item32.h +++ b/engines/sci/graphics/screen_item32.h @@ -213,7 +213,7 @@ public: ScreenItem(const reg_t screenItem); ScreenItem(const reg_t plane, const CelInfo32 &celInfo); ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect); - ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect, const ScaleInfo &scaleInfo); + ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo); ScreenItem(const ScreenItem &other); void operator=(const ScreenItem &); diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index 8e3222f580..c1335b9b4d 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -40,10 +40,9 @@ namespace Sci { int16 GfxText32::_defaultFontId = 0; -GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen) : +GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) : _segMan(segMan), _cache(fonts), - _screen(screen), _scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), _scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), // Not a typo, the original engine did not initialise height, only width @@ -179,6 +178,11 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & return _bitmap; } +reg_t GfxText32::createTitledBitmap(const int16 width, const int16 height, const Common::Rect &textRect, const Common::String &text, const int16 foreColor, const int16 backColor, const int16 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, Common::String &title, const int16 titleForeColor, const int16 titleBackColor, const GuiResourceId titleFontId, const bool doScaling, reg_t *outBitmapObject) { + warning("TODO: createTitledBitmap incomplete !"); + return createFontBitmap(width, height, textRect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, false, doScaling, outBitmapObject); +} + void GfxText32::setFont(const GuiResourceId fontId) { // NOTE: In SCI engine this calls FontMgr::BuildFontTable and then a font // table is built on the FontMgr directly; instead, because we already have @@ -202,7 +206,7 @@ void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint buffer.frameRect(targetRect, color); } -void GfxText32::drawChar(const uint8 charIndex) { +void GfxText32::drawChar(const char charIndex) { byte *bitmap = _segMan->getHunkPointer(_bitmap); byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); @@ -210,7 +214,7 @@ void GfxText32::drawChar(const uint8 charIndex) { _drawPosition.x += _font->getCharWidth(charIndex); } -uint16 GfxText32::getCharWidth(const uint8 charIndex, const bool doScaling) const { +uint16 GfxText32::getCharWidth(const char charIndex, const bool doScaling) const { uint16 width = _font->getCharWidth(charIndex); if (doScaling) { width = scaleUpWidth(width); @@ -253,6 +257,11 @@ void GfxText32::drawTextBox() { } } +void GfxText32::drawTextBox(const Common::String &text) { + _text = text; + drawTextBox(); +} + void GfxText32::drawText(const uint index, uint length) { assert(index + length <= _text.size()); @@ -309,6 +318,51 @@ void GfxText32::drawText(const uint index, uint length) { } } +void GfxText32::invertRect(const reg_t bitmap, int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling) { + Common::Rect targetRect = rect; + if (doScaling) { + bitmapStride = bitmapStride * _scaledWidth / g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + targetRect = scaleRect(rect); + } + + byte *bitmapData = _segMan->getHunkPointer(bitmap); + + // NOTE: SCI code is super weird here; it seems to be trying to look at the + // entire size of the bitmap including the header, instead of just the pixel + // data size. We just look at the pixel size. This function generally is an + // odd duck since the stride dimension for a bitmap is built in to the bitmap + // header, so perhaps it was once an unheadered bitmap format and this + // function was never updated to match? Or maybe they exploit the + // configurable stride length somewhere else to do stair stepping inverts... + uint32 invertSize = targetRect.height() * bitmapStride + targetRect.width(); + uint32 bitmapSize = READ_SCI11ENDIAN_UINT32(bitmapData + 12); + + if (invertSize >= bitmapSize) { + error("InvertRect too big: %u >= %u", invertSize, bitmapSize); + } + + // NOTE: Actual engine just added the bitmap header size hardcoded here + byte *pixel = bitmapData + READ_SCI11ENDIAN_UINT32(bitmapData + 28) + bitmapStride * targetRect.top + targetRect.left; + + int16 stride = bitmapStride - targetRect.width(); + int16 targetHeight = targetRect.height(); + int16 targetWidth = targetRect.width(); + + for (int16 y = 0; y < targetHeight; ++y) { + for (int16 x = 0; x < targetWidth; ++x) { + if (*pixel == foreColor) { + *pixel = backColor; + } else if (*pixel == backColor) { + *pixel = foreColor; + } + + ++pixel; + } + + pixel += stride; + } +} + uint GfxText32::getLongest(uint *charIndex, const int16 width) { assert(width > 0); @@ -543,7 +597,7 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, } void GfxText32::erase(const Common::Rect &rect, const bool doScaling) { - Common::Rect targetRect = doScaling ? rect : scaleRect(rect); + Common::Rect targetRect = doScaling ? scaleRect(rect) : rect; byte *bitmap = _segMan->getHunkPointer(_bitmap); byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); @@ -556,10 +610,7 @@ void GfxText32::erase(const Common::Rect &rect, const bool doScaling) { } int16 GfxText32::getStringWidth(const Common::String &text) { - // TODO: The fact that this double-scales the text makes it - // seem pretty unlikely that this is ever called in real life - error("Called weirdo getStringWidth (FontMgr::StringWidth)"); - return scaleUpWidth(getTextWidth(text, 0, 10000)); + return getTextWidth(text, 0, 10000); } } // End of namespace Sci diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h index 5de54d318f..9e268e3485 100644 --- a/engines/sci/graphics/text32.h +++ b/engines/sci/graphics/text32.h @@ -45,7 +45,6 @@ class GfxText32 { private: SegManager *_segMan; GfxCache *_cache; - GfxScreen *_screen; /** * The resource ID of the default font used by the game. @@ -111,11 +110,6 @@ private: */ TextAlign _alignment; - /** - * The memory handle of the currently active bitmap. - */ - reg_t _bitmap; - int16 _field_20; /** @@ -133,18 +127,10 @@ private: Common::Point _drawPosition; void drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling); - void drawTextBox(); - void erase(const Common::Rect &rect, const bool doScaling); - void drawChar(const uint8 charIndex); - uint16 getCharWidth(const uint8 charIndex, const bool doScaling) const; + void drawChar(const char charIndex); void drawText(const uint index, uint length); - inline int scaleUpWidth(int value) const { - const int scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; - return (value * scriptWidth + _scaledWidth - 1) / _scaledWidth; - } - /** * Gets the length of the longest run of text available * within the currently loaded text, starting from the @@ -162,24 +148,23 @@ private: */ int16 getTextWidth(const uint index, uint length) const; - /** - * Gets the pixel width of a substring of the currently - * loaded text, with scaling. - */ - int16 getTextWidth(const Common::String &text, const uint index, const uint length); - inline Common::Rect scaleRect(const Common::Rect &rect) { Common::Rect scaledRect(rect); int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; Ratio scaleX(_scaledWidth, scriptWidth); Ratio scaleY(_scaledHeight, scriptHeight); - mul(scaledRect, scaleX, scaleY); + mulinc(scaledRect, scaleX, scaleY); return scaledRect; } public: - GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen); + GfxText32(SegManager *segMan, GfxCache *fonts); + + /** + * The memory handle of the currently active bitmap. + */ + reg_t _bitmap; /** * The size of the x-dimension of the coordinate system @@ -213,17 +198,68 @@ public: */ reg_t createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed, reg_t *outBitmapObject); + /** + * Creates a font bitmap with a title. + */ + reg_t createTitledBitmap(const int16 width, const int16 height, const Common::Rect &textRect, const Common::String &text, const int16 foreColor, const int16 backColor, const int16 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, Common::String &title, const int16 titleForeColor, const int16 titleBackColor, const GuiResourceId titleFontId, const bool doScaling, reg_t *outBitmapObject); + + inline int scaleUpWidth(int value) const { + const int scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + return (value * scriptWidth + _scaledWidth - 1) / _scaledWidth; + } + + inline int scaleUpHeight(int value) const { + const int scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + return (value * scriptHeight + _scaledHeight - 1) / _scaledHeight; + } + + /** + * Draws the text to the bitmap. + */ + void drawTextBox(); + + /** + * Draws the given text to the bitmap. + * + * @note The original engine holds a reference to a + * shared string which lets the text be updated from + * outside of the font manager. Instead, we give this + * extra signature to send the text to draw. + * + * TODO: Use shared string instead? + */ + void drawTextBox(const Common::String &text); + + /** + * Erases the given rect by filling with the background + * color. + */ + void erase(const Common::Rect &rect, const bool doScaling); + + void invertRect(const reg_t bitmap, const int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling); + /** * Sets the font to be used for rendering and * calculation of text dimensions. */ void setFont(const GuiResourceId fontId); + /** + * Gets the width of a character. + */ + uint16 getCharWidth(const char charIndex, const bool doScaling) const; + /** * Retrieves the width and height of a block of text. */ Common::Rect getTextSize(const Common::String &text, const int16 maxWidth, bool doScaling); + /** + * Gets the pixel width of a substring of the currently + * loaded text, with scaling. + */ + int16 getTextWidth(const Common::String &text, const uint index, const uint length); + /** * Retrieves the width of a line of text. */ diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 52188db0fb..7c985dfab4 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -705,7 +705,7 @@ void SciEngine::initGraphics() { _gfxPaint = _gfxPaint32; _robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh); _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette32, _gfxPaint32); - _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache, _gfxScreen); + _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache); _gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32); _gfxFrameout->run(); } else { -- cgit v1.2.3