Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions common/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,17 @@ void MainWindow::processMouse(MouseEvent& event, bool enable) {
}

void MainWindow::tickMouse(uint64_t dt) {
if(rootMenu.isActive() || dialogs.isActive() || inventory.isActive()) {
uiPointer = true;
}
else if(uiPointer) {
// The UI pointer moves freely, so mouse look restarts from the window center instead of where it was left.
uiPointer = false;
dMouse = Point();
if(SystemApi::isFullscreen(hwnd()))
setCursorPosition(Point(w()/2,h()/2));
}

auto camera = Gothic::inst().camera();
if(dialogs.hasContent() || Gothic::inst().isPause() || camera==nullptr || camera->isCutscene()) {
dMouse = Point();
Expand Down
1 change: 1 addition & 0 deletions common/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class MainWindow : public Tempest::Window {

Tempest::Widget* uiKeyUp=nullptr;
Tempest::Point dMouse;
bool uiPointer=false;
PlayerControl player;
uint64_t lastTick=0;

Expand Down
57 changes: 57 additions & 0 deletions common/ui/dialogmenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <cassert>

#include "utils/gthfont.h"
#include "utils/mouseutil.h"
#include "utils/string_frm.h"
#include "world/objects/npc.h"
#include "gothic.h"
Expand Down Expand Up @@ -85,6 +86,10 @@ void DialogMenu::tick(uint64_t dt) {
remPrint-=dt;
}

const auto cursor = hasPointer() ? CursorShape::Arrow : CursorShape::Hidden;
if(cursorShape()!=cursor)
setCursorShape(cursor);

if(isChoiceMenuActive()) {
if(choiceAnimTime+dt>ANIM_TIME)
choiceAnimTime = ANIM_TIME; else
Expand Down Expand Up @@ -554,13 +559,65 @@ void DialogMenu::mouseDownEvent(MouseEvent &event) {
}

if(event.button==MouseEvent::ButtonLeft) {
// Clicking beside the choices must not pick the highlighted one.
if(hasPointer()) {
const size_t id = choiceAt(event.pos());
if(id>=choice.size())
return;
dlgSel = id;
}
onSelect();
}
if(event.button==MouseEvent::ButtonRight) {
skipPhrase();
}
}

void DialogMenu::mouseMoveEvent(MouseEvent& event) {
if(state==State::Idle || trade.isOpen()!=InventoryMenu::State::Closed || !MouseUtil::isPointerEnabled()) {
event.ignore();
return;
}
// Moves during a dialog are accepted, so the game does not see them as mouse look.
if(!hasPointer())
return;
const size_t id = choiceAt(event.pos());
if(id<choice.size() && id!=dlgSel) {
dlgSel = id;
update();
}
}

bool DialogMenu::hasPointer() const {
return MouseUtil::isPointerEnabled() && isChoiceMenuActive();
}

size_t DialogMenu::choiceAt(Point pos) const {
// Same layout as paintChoice
const float scale = Gothic::interfaceScale(this);
const int padd = int(20*scale);
const int dw = std::min(w(),int(600*scale));
const auto fnt = Resources::dialogFont(scale);

int dh = padd*2;
for(auto& c:choice)
dh += fnt.textSize(dw-padd, c.title).h;

const int x = (w()-dw)/2;
if(pos.x<x || pos.x>=x+dw)
return size_t(-1);

// Text is drawn from its baseline, one font height below the top of its line.
int top = h()-dh-int(20*scale)+padd*2-fnt.pixelSize();
for(size_t i=0; i<choice.size(); ++i) {
const int lineH = fnt.textSize(dw-padd, choice[i].title).h;
if(pos.y>=top && pos.y<top+lineH)
return i;
top += lineH;
}
return size_t(-1);
}

void DialogMenu::mouseWheelEvent(MouseEvent &e) {
if(state==State::Idle || trade.isOpen()!=InventoryMenu::State::Closed){
e.ignore();
Expand Down
3 changes: 3 additions & 0 deletions common/ui/dialogmenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@ class DialogMenu : public Tempest::Widget {
void paintChoice(Tempest::PaintEvent& e);

void mouseDownEvent (Tempest::MouseEvent& event) override;
void mouseMoveEvent (Tempest::MouseEvent& event) override;
void mouseWheelEvent(Tempest::MouseEvent& event) override;

void onSelect();
size_t choiceAt(Tempest::Point pos) const;
bool hasPointer() const;
void setupSettings();
bool isChoiceMenuActive() const;

Expand Down
135 changes: 117 additions & 18 deletions common/ui/gamemenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "utils/gthfont.h"
#include "utils/fileutil.h"
#include "utils/keycodec.h"
#include "utils/mouseutil.h"
#include "game/definitions/musicdefinitions.h"
#include "game/serialize.h"
#include "game/savegameheader.h"
Expand Down Expand Up @@ -78,20 +79,40 @@ struct GameMenu::ListContentDialog : Dialog {
struct GameMenu::ListViewDialog : Dialog {
ListViewDialog(GameMenu& owner, Item& list):owner(owner),list(list){
setFocusPolicy(ClickFocus);
setCursorShape(CursorShape::Hidden);
setCursorShape(MouseUtil::isPointerEnabled() ? CursorShape::Arrow : CursorShape::Hidden);
setFocus(true);
status = toStatus(list.handle->user_string[0]);
}

void mouseDownEvent(MouseEvent& e) override {
if(e.button==Event::ButtonLeft) {
// With the pointer, a click opens the quest under it; clicking beside the list does nothing.
if(MouseUtil::isPointerEnabled() && !selectAt(e.pos()))
return;
showQuest();
}
if(e.button==Event::ButtonRight) {
close();
}
}

void mouseMoveEvent(MouseEvent& e) override {
if(MouseUtil::isPointerEnabled())
selectAt(e.pos());
}

bool selectAt(Point pos) {
// The list is drawn by the menu; this dialog only covers it.
const int32_t id = owner.questAt(list, pos+mapToRoot(Point())-owner.mapToRoot(Point()));
if(id<0)
return false;
if(list.value!=id) {
list.value = id;
update();
}
return true;
}

void showQuest() {
auto prev = owner.curItem;
auto* next = owner.selectedContentItem(&list);
Expand Down Expand Up @@ -529,6 +550,40 @@ void GameMenu::drawSlider(Painter& p, Item& it, int x, int y, int sw, int sh) {
0,0,p.brush().w(),p.brush().h());
}

int32_t GameMenu::questAt(const Item& list, Point pos) const {
auto* log = Gothic::inst().questLog();
if(log==nullptr)
return -1;
// Same layout as drawItem and drawQuestList
const auto& item = *list.handle;
const auto rect = itemRect(list);
const int px = int(float(w()*item.frame_sizex)/scriptDiv);
const int py = int(float(h()*item.frame_sizey)/scriptDiv);
const int x = rect.x+px, y = rect.y+py, lw = rect.w-2*px, lh = rect.h-2*py;
if(pos.x<x || pos.x>=x+lw || pos.y<y)
return -1;

const auto st = toStatus(item.user_string[0]);
const float scale = Gothic::interfaceScale(this);
auto& fnt = Resources::font(item.fontname,Resources::FontType::Normal,scale);
int32_t id = -1;
int top = y;
for(size_t i=0; i<log->questCount(); ++i) {
auto& quest = log->quest(log->questCount()-i-1);
if(!isCompatible(quest,st))
continue;
if(++id<list.scroll)
continue;
const int rowH = fnt.textSize(lw,quest.name).h;
if(top+rowH>y+lh)
break;
if(pos.y<top+rowH)
return id;
top += rowH;
}
return -1;
}

void GameMenu::drawQuestList(Painter& p, Item& it, int x, int y, int w, int h,
const QuestLog& log, QuestStat st) {
int itY = y;
Expand Down Expand Up @@ -594,10 +649,30 @@ Rect GameMenu::itemRect(const Item& it) const {
int(float(w()*dimx)/scriptDiv),int(float(h()*dimy)/scriptDiv));
}

GameMenu::Item* GameMenu::touchHitTest(Item& p, Point pos) {
Rect GameMenu::hitRect(const Item& it, bool textOnly) {
auto rect = itemRect(it);
if(it.handle->type==zenkit::MenuItemType::SLIDER)
return rect;
// Without an explicit height the script box spans several rows, so only the text line counts.
const auto& fnt = getTextFont(it);
if(textOnly || it.handle->dim_y==-1)
rect.h = fnt.pixelSize();
if(!textOnly || it.handle->type==zenkit::MenuItemType::INPUT || (it.handle->flags & zenkit::MenuItemFlag::MULTILINE))
return rect;
getText(it,textBuf);
const int tw = fnt.textSize(std::string_view(textBuf.data())).w;
if(tw>0 && tw<rect.w) {
if(it.handle->flags & zenkit::MenuItemFlag::CENTERED)
rect.x += (rect.w-tw)/2;
rect.w = tw;
}
return rect;
}

GameMenu::Item* GameMenu::touchHitTest(Item& p, Point pos, bool textOnly) {
auto* it = &p;
while(it!=nullptr) {
auto rect = itemRect(*it);
auto rect = hitRect(*it,textOnly);
if(rect.contains(pos))
return it;
if(it->handle->flags & zenkit::MenuItemFlag::EFFECTS) {
Expand All @@ -611,10 +686,14 @@ GameMenu::Item* GameMenu::touchHitTest(Item& p, Point pos) {
return nullptr;
}

void GameMenu::onBeginTouch(Point pos) {
touchSlider = nullptr;
touchActive = false;
GameMenu::Item* GameMenu::hitTest(Point pos, uint32_t& index) {
// Drawn text wins on shared rows such as Back and "More keys"; the script box still makes short entries easy to hit.
if(auto hit = hitTest(pos,index,true))
return hit;
return hitTest(pos,index,false);
}

GameMenu::Item* GameMenu::hitTest(Point pos, uint32_t& index, bool textOnly) {
// reverse of draw order
for(uint32_t i = zenkit::IMenu::item_count; i-- > 0;) {
if(hItems[i].handle==nullptr)
Expand All @@ -624,20 +703,40 @@ void GameMenu::onBeginTouch(Point pos) {
continue;
if(!isSelectable(item))
continue;
auto hit = touchHitTest(hItems[i], pos);
if(hit==nullptr)
continue;
setSelection(int(i), -1);
if(auto s = selectedItem()) {
updateSavThumb(*s);
touchActive = true;
if(auto hit = touchHitTest(hItems[i], pos, textOnly)) {
index = i;
return hit;
}
if(hit->handle->type==zenkit::MenuItemType::SLIDER) {
touchSlider = hit;
break;
}
break;
}
return nullptr;
}

void GameMenu::onBeginTouch(Point pos) {
touchSlider = nullptr;
touchActive = false;

uint32_t index = 0;
auto hit = hitTest(pos, index);
if(hit==nullptr)
return;
setSelection(int(index), -1);
if(auto s = selectedItem()) {
updateSavThumb(*s);
touchActive = true;
}
if(hit->handle->type==zenkit::MenuItemType::SLIDER)
touchSlider = hit;
}

void GameMenu::onHover(Point pos) {
uint32_t index = 0;
if(hitTest(pos, index)==nullptr || index==curItem)
return;
// Selection runs the item's select commands, so only change it when another item is hovered.
setSelection(int(index), -1);
if(auto s = selectedItem())
updateSavThumb(*s);
update();
}

void GameMenu::onDragTouch(Point pos) {
Expand Down
7 changes: 6 additions & 1 deletion common/ui/gamemenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class GameMenu : public Tempest::Widget {
void onBeginTouch(Tempest::Point pos);
void onDragTouch(Tempest::Point pos);
void onEndTouch(bool activate);
void onHover(Tempest::Point pos);
void onTick();
void processMusicTheme();

Expand Down Expand Up @@ -99,7 +100,11 @@ class GameMenu : public Tempest::Widget {
void initItems();
void getText(const Item &it, std::vector<char>& out);
const GthFont& getTextFont(const Item &it);
Item* touchHitTest(Item& p, Tempest::Point pos);
Tempest::Rect hitRect(const Item& it, bool textOnly);
int32_t questAt(const Item& list, Tempest::Point pos) const;
Item* touchHitTest(Item& p, Tempest::Point pos, bool textOnly);
Item* hitTest(Tempest::Point pos, uint32_t& index);
Item* hitTest(Tempest::Point pos, uint32_t& index, bool textOnly);

static bool isSelectable(const std::shared_ptr<zenkit::IMenuItem>& item);
static bool isHorSelectable(const std::shared_ptr<zenkit::IMenuItem>& item);
Expand Down
Loading
Loading