Skip to content
Merged
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
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added
- Implement `GameSubgameRep` (C++) and `Subgame` (Python), a first-class object representing a subgame. (#585)
- Games can be materialised directly from OpenSpiel games if `pyspiel` is installed. (#917)
- Magnify events are now supported in GUI for zooming in/out on trees.

### Fixed
- Corrected resizing of row and column index labels in strategic form so pivoting works correctly. (#844)
Expand Down
111 changes: 104 additions & 7 deletions src/gui/efgdisplay.cc
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ BEGIN_EVENT_TABLE(EfgDisplay, wxScrolledWindow)
EVT_MOTION(EfgDisplay::OnMouseMotion)
EVT_LEFT_DOWN(EfgDisplay::OnLeftClick)
EVT_LEFT_DCLICK(EfgDisplay::OnLeftDoubleClick)
EVT_MAGNIFY(EfgDisplay::OnMagnify)
EVT_RIGHT_DOWN(EfgDisplay::OnRightClick)
EVT_KEY_DOWN(EfgDisplay::OnKeyEvent)
EVT_SIZE(EfgDisplay::OnSize)
Expand Down Expand Up @@ -612,16 +613,34 @@ void EfgDisplay::RefreshTree()
Refresh();
}

constexpr int kScrollPixelsPerUnit = 1;

void EfgDisplay::AdjustScrollbarSteps()
{
int width, height;
GetClientSize(&width, &height);
int oldPixelsPerUnitX, oldPixelsPerUnitY;
GetScrollPixelsPerUnit(&oldPixelsPerUnitX, &oldPixelsPerUnitY);

int scrollX, scrollY;
GetViewStart(&scrollX, &scrollY);

SetScrollbars(50, 50, LayoutToDevice(m_layout.MaxX()) / 50 + 1,
LayoutToDevice(m_layout.MaxY()) / 50 + 1, scrollX, scrollY);
const int currentPixelX = scrollX * oldPixelsPerUnitX;
const int currentPixelY = scrollY * oldPixelsPerUnitY;

int clientWidth, clientHeight;
GetClientSize(&clientWidth, &clientHeight);

const int virtualWidth = LayoutToDevice(m_layout.MaxX());
const int virtualHeight = LayoutToDevice(m_layout.MaxY());

const int maxPixelX = std::max(0, virtualWidth - clientWidth);
const int maxPixelY = std::max(0, virtualHeight - clientHeight);

const int clampedPixelX = std::clamp(currentPixelX, 0, maxPixelX);
const int clampedPixelY = std::clamp(currentPixelY, 0, maxPixelY);

SetScrollbars(kScrollPixelsPerUnit, kScrollPixelsPerUnit,
virtualWidth / kScrollPixelsPerUnit + 1, virtualHeight / kScrollPixelsPerUnit + 1,
clampedPixelX / kScrollPixelsPerUnit, clampedPixelY / kScrollPixelsPerUnit);
}

void EfgDisplay::FitZoom()
Expand All @@ -640,14 +659,85 @@ void EfgDisplay::FitZoom()
Refresh();
}

void EfgDisplay::SetZoom(int p_zoom)
namespace {

constexpr int kMinZoom = 10;
constexpr int kMaxZoom = 150;
constexpr int kZoomStep = 10;
constexpr int kScrollPixelsPerUnit = 1;

int ClampZoom(int p_zoom) { return std::clamp(p_zoom, kMinZoom, kMaxZoom); }

} // namespace

void EfgDisplay::SetZoom(int p_zoom, bool p_keepSelectionVisible)
{
m_zoom = p_zoom;
const int zoom = ClampZoom(p_zoom);
if (zoom == m_zoom) {
return;
}

m_zoom = zoom;
AdjustScrollbarSteps();
EnsureNodeVisible(m_doc->GetSelectNode());

if (p_keepSelectionVisible) {
EnsureNodeVisible(m_doc->GetSelectNode());
}

Refresh();
}

void EfgDisplay::ZoomByFactor(double p_factor, const wxPoint &p_clientPoint)
{
if (p_factor <= 0.0) {
return;
}

const int oldZoom = GetZoom();
const int newZoom = ClampZoom(static_cast<int>(std::lround(oldZoom * p_factor)));

if (newZoom == oldZoom) {
return;
}

int unscrolledX, unscrolledY;
CalcUnscrolledPosition(p_clientPoint.x, p_clientPoint.y, &unscrolledX, &unscrolledY);

const double oldScale = GetZoom() / 100.0;
const double layoutX = unscrolledX / oldScale;
const double layoutY = unscrolledY / oldScale;

SetZoom(newZoom, false);

const double newScale = GetZoom() / 100.0;
const int targetUnscrolledX = static_cast<int>(std::lround(layoutX * newScale));
const int targetUnscrolledY = static_cast<int>(std::lround(layoutY * newScale));

int pixelsPerUnitX, pixelsPerUnitY;
GetScrollPixelsPerUnit(&pixelsPerUnitX, &pixelsPerUnitY);

if (pixelsPerUnitX <= 0 || pixelsPerUnitY <= 0) {
return;
}

const int targetScrollX = targetUnscrolledX - p_clientPoint.x;
const int targetScrollY = targetUnscrolledY - p_clientPoint.y;

int clientWidth, clientHeight;
GetClientSize(&clientWidth, &clientHeight);

int virtualWidth, virtualHeight;
GetVirtualSize(&virtualWidth, &virtualHeight);

const int maxPixelX = std::max(0, virtualWidth - clientWidth);
const int maxPixelY = std::max(0, virtualHeight - clientHeight);

const int clampedScrollX = std::clamp(targetScrollX, 0, maxPixelX);
const int clampedScrollY = std::clamp(targetScrollY, 0, maxPixelY);

Scroll(clampedScrollX / pixelsPerUnitX, clampedScrollY / pixelsPerUnitY);
}

void EfgDisplay::OnDraw(wxDC &p_dc)
{
p_dc.SetUserScale(GetScale(), GetScale());
Expand Down Expand Up @@ -873,6 +963,13 @@ void EfgDisplay::OnLeftDoubleClick(wxMouseEvent &p_event)
}
}

void EfgDisplay::OnMagnify(wxMouseEvent &p_event)
{
if (const double factor = 1.0 + p_event.GetMagnification(); factor > 0.0) {
ZoomByFactor(factor, p_event.GetPosition());
}
}

#include "bitmaps/tree.xpm"
#include "bitmaps/move.xpm"

Expand Down
6 changes: 4 additions & 2 deletions src/gui/efgdisplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ class EfgDisplay final : public wxScrolledWindow, public GameView {
TreePayoffEditor *m_payoffEditor;
bool m_pendingInitialZoom{true};

// Private Functions
void MakeMenus();
void AdjustScrollbarSteps();

Expand All @@ -70,6 +69,7 @@ class EfgDisplay final : public wxScrolledWindow, public GameView {
void OnLeftClick(wxMouseEvent &);
void OnRightClick(wxMouseEvent &);
void OnLeftDoubleClick(wxMouseEvent &);
void OnMagnify(wxMouseEvent &);
void OnKeyEvent(wxKeyEvent &);
/// Payoff editor changes accepted with enter
void OnAcceptPayoffEdit(wxCommandEvent &);
Expand All @@ -86,14 +86,16 @@ class EfgDisplay final : public wxScrolledWindow, public GameView {
/// @brief Scroll the viewport such that the node is at the specified fraction of the viewport
void FocusNode(const GameNode &p_node, double p_xFrac = 0.5, double p_yFrac = 0.5);

void ZoomByFactor(double p_factor, const wxPoint &p_clientPoint);

public:
EfgDisplay(wxWindow *p_parent, GameDocument *p_doc);

void OnDraw(wxDC &dc) override;
void OnDraw(wxDC &, double);

int GetZoom() const { return m_zoom; }
void SetZoom(int p_zoom);
void SetZoom(int p_zoom, bool p_keepSelectionVisible = true);
void FitZoom();

double GetScale() const { return 0.01 * m_zoom; }
Expand Down
26 changes: 14 additions & 12 deletions src/gui/efgpanel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//

#include <algorithm>

#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
Expand Down Expand Up @@ -500,22 +502,24 @@ EfgPanel::EfgPanel(wxWindow *p_parent, GameDocument *p_doc)
wxWindowBase::Layout();
}

namespace {

constexpr int kMinZoom = 10;
constexpr int kMaxZoom = 150;
constexpr int kZoomStep = 10;

int ClampZoom(int p_zoom) { return std::clamp(p_zoom, kMinZoom, kMaxZoom); }

} // namespace

void EfgPanel::OnViewZoomIn(wxCommandEvent &)
{
int zoom = m_treeWindow->GetZoom();
if (zoom < 150) {
zoom += 10;
}
m_treeWindow->SetZoom(zoom);
m_treeWindow->SetZoom(ClampZoom(m_treeWindow->GetZoom() + kZoomStep));
}

void EfgPanel::OnViewZoomOut(wxCommandEvent &)
{
int zoom = m_treeWindow->GetZoom();
if (zoom > 10) {
zoom -= 10;
}
m_treeWindow->SetZoom(zoom);
m_treeWindow->SetZoom(ClampZoom(m_treeWindow->GetZoom() - kZoomStep));
}

void EfgPanel::OnViewZoom100(wxCommandEvent &) { m_treeWindow->SetZoom(100); }
Expand Down Expand Up @@ -605,8 +609,6 @@ void EfgPanel::RenderGame(wxDC &p_dc, int p_marginX, int p_marginY)
auto posY = ((h - (maxY * scale)) / 2.0);
p_dc.SetDeviceOrigin(static_cast<int>(posX), static_cast<int>(posY));

printf("Drawing with scale %f\n", scale);

// Draw!
m_treeWindow->OnDraw(p_dc, scale);
}
Expand Down
52 changes: 32 additions & 20 deletions src/gui/gameframe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -249,16 +249,18 @@ GameFrame::GameFrame(wxWindow *p_parent, GameDocument *p_doc)
MakeMenus();
MakeToolbar();

wxAcceleratorEntry entries[8];
wxAcceleratorEntry entries[10];
entries[0].Set(wxACCEL_CTRL, 'o', wxID_OPEN);
entries[1].Set(wxACCEL_CTRL, 's', wxID_SAVE);
entries[2].Set(wxACCEL_CTRL | wxACCEL_SHIFT, 's', wxID_SAVEAS);
entries[3].Set(wxACCEL_CTRL, 'p', wxID_PRINT);
entries[4].Set(wxACCEL_CTRL, 'w', wxID_CLOSE);
entries[5].Set(wxACCEL_CTRL, 'q', wxID_EXIT);
entries[6].Set(wxACCEL_CTRL, '+', GBT_MENU_VIEW_ZOOMIN);
entries[7].Set(wxACCEL_CTRL, '-', GBT_MENU_VIEW_ZOOMOUT);
const wxAcceleratorTable accel(8, entries);
entries[7].Set(wxACCEL_CTRL, '=', GBT_MENU_VIEW_ZOOMIN);
entries[8].Set(wxACCEL_CTRL, '-', GBT_MENU_VIEW_ZOOMOUT);
entries[9].Set(wxACCEL_CTRL, '0', GBT_MENU_VIEW_ZOOM100);
const wxAcceleratorTable accel(10, entries);
wxWindowBase::SetAcceleratorTable(accel);

m_splitter = new wxSplitterWindow(this, wxID_ANY);
Expand Down Expand Up @@ -342,8 +344,16 @@ void GameFrame::OnUpdate()
}
menuBar->Check(GBT_MENU_VIEW_PROFILES, m_splitter->IsSplit());
GetToolBar()->ToggleTool(GBT_MENU_VIEW_PROFILES, m_splitter->IsSplit());
menuBar->Enable(GBT_MENU_VIEW_ZOOMIN, m_efgPanel && m_efgPanel->IsShown());
menuBar->Enable(GBT_MENU_VIEW_ZOOMOUT, m_efgPanel && m_efgPanel->IsShown());

const bool canZoomTree = m_efgPanel && m_efgPanel->IsShown();
menuBar->Enable(GBT_MENU_VIEW_ZOOMIN, canZoomTree);
menuBar->Enable(GBT_MENU_VIEW_ZOOMOUT, canZoomTree);
menuBar->Enable(GBT_MENU_VIEW_ZOOMFIT, canZoomTree);
menuBar->Enable(GBT_MENU_VIEW_ZOOM100, canZoomTree);

GetToolBar()->EnableTool(GBT_MENU_VIEW_ZOOMIN, canZoomTree);
GetToolBar()->EnableTool(GBT_MENU_VIEW_ZOOMOUT, canZoomTree);
GetToolBar()->EnableTool(GBT_MENU_VIEW_ZOOMFIT, canZoomTree);
}

//--------------------------------------------------------------------
Expand Down Expand Up @@ -472,13 +482,13 @@ void GameFrame::MakeMenus()
viewMenu->Check(GBT_MENU_VIEW_PROFILES, false);
viewMenu->AppendSeparator();

AppendBitmapItem(viewMenu, GBT_MENU_VIEW_ZOOMIN, _("Zoom &in"),
AppendBitmapItem(viewMenu, GBT_MENU_VIEW_ZOOMIN, _("Zoom &In\tCtrl-+"),
_("Increase display magnification"), wxBitmap(zoomin_xpm));
AppendBitmapItem(viewMenu, GBT_MENU_VIEW_ZOOMOUT, _("Zoom &out"),
AppendBitmapItem(viewMenu, GBT_MENU_VIEW_ZOOMOUT, _("Zoom &Out\tCtrl--"),
_("Decrease display magnification"), wxBitmap(zoomout_xpm));
AppendBitmapItem(viewMenu, GBT_MENU_VIEW_ZOOM100, _("&Zoom 1:1"), _("Set magnification to 1:1"),
wxBitmap(zoom1_xpm));
AppendBitmapItem(viewMenu, GBT_MENU_VIEW_ZOOMFIT, _("&Fit tree to window"),
AppendBitmapItem(viewMenu, GBT_MENU_VIEW_ZOOM100, _("&Actual Size\tCtrl-0"),
_("Set magnification to 1:1"), wxBitmap(zoom1_xpm));
AppendBitmapItem(viewMenu, GBT_MENU_VIEW_ZOOMFIT, _("Zoom to &Fit"),
_("Rescale to show entire tree in window"), wxBitmap(zoomfit_xpm));

viewMenu->AppendSeparator();
Expand Down Expand Up @@ -507,7 +517,7 @@ void GameFrame::MakeMenus()
AppendBitmapItem(toolsMenu, GBT_MENU_TOOLS_EQUILIBRIUM, _("&Equilibrium"),
_("Compute Nash equilibria and refinements"), wxBitmap(calc_xpm));

toolsMenu->Append(GBT_MENU_TOOLS_QRE, _("&Qre"), _("Compute quantal response equilibria"));
toolsMenu->Append(GBT_MENU_TOOLS_QRE, _("&QRE"), _("Compute quantal response equilibria"));

auto *helpMenu = new wxMenu;
AppendBitmapItem(helpMenu, wxID_ABOUT, _("&About Gambit"), _("About Gambit"),
Expand Down Expand Up @@ -563,9 +573,10 @@ void GameFrame::MakeToolbar()
wxITEM_NORMAL, _("Zoom in"), _("Increase magnification"), nullptr);
toolBar->AddTool(GBT_MENU_VIEW_ZOOMOUT, wxEmptyString, wxBitmap(zoomout_xpm), wxNullBitmap,
wxITEM_NORMAL, _("Zoom out"), _("Decrease magnification"), nullptr);
toolBar->AddTool(GBT_MENU_VIEW_ZOOM100, wxEmptyString, wxBitmap(zoom1_xpm), wxNullBitmap,
wxITEM_NORMAL, _("Actual size"), _("Set magnification to 1:1"), nullptr);
toolBar->AddTool(GBT_MENU_VIEW_ZOOMFIT, wxEmptyString, wxBitmap(zoomfit_xpm), wxNullBitmap,
wxITEM_NORMAL, _("Fit to window"), _("Set magnification to see entrie tree"),
nullptr);
wxITEM_NORMAL, _("Zoom to fit"), _("Fit the tree in the window"), nullptr);
}

toolBar->AddSeparator();
Expand All @@ -585,7 +596,7 @@ void GameFrame::MakeToolbar()
_("Display the reduced strategic representation of the game"), nullptr);
}
toolBar->AddTool(GBT_MENU_VIEW_PROFILES, wxEmptyString, wxBitmap(profiles_xpm), wxNullBitmap,
wxITEM_NORMAL, _("View the list of computed strategy profiles"),
wxITEM_CHECK, _("View the list of computed strategy profiles"),
_("Show or hide the list of computed strategy profiles"), nullptr);
toolBar->AddTool(GBT_MENU_TOOLS_EQUILIBRIUM, wxEmptyString, wxBitmap(calc_xpm), wxNullBitmap,
wxITEM_NORMAL, _("Compute Nash equilibria of this game"),
Expand Down Expand Up @@ -1058,7 +1069,9 @@ void GameFrame::OnViewProfiles(wxCommandEvent &p_event)
void GameFrame::OnViewZoom(wxCommandEvent &p_event)
{
// All zoom events get passed along to the panel
wxPostEvent(m_efgPanel, p_event);
if (m_efgPanel && m_efgPanel->IsShown()) {
wxPostEvent(m_efgPanel, p_event);
}
}

void GameFrame::OnViewStrategic(wxCommandEvent &p_event)
Expand Down Expand Up @@ -1112,15 +1125,14 @@ void GameFrame::OnViewStrategic(wxCommandEvent &p_event)
m_efgPanel->SetFocus();
}

const bool canZoomTree = m_efgPanel && m_efgPanel->IsShown();

GetMenuBar()->Check(GBT_MENU_VIEW_STRATEGIC, m_nfgPanel->IsShown());
GetMenuBar()->Enable(GBT_MENU_VIEW_ZOOMIN, !p_event.IsChecked());
GetMenuBar()->Enable(GBT_MENU_VIEW_ZOOMOUT, !p_event.IsChecked());
GetMenuBar()->Enable(GBT_MENU_TOOLS_DOMINANCE, m_nfgPanel->IsShown());

GetToolBar()->ToggleTool(GBT_MENU_VIEW_STRATEGIC, p_event.IsChecked());
GetToolBar()->EnableTool(GBT_MENU_VIEW_ZOOMIN, !p_event.IsChecked());
GetToolBar()->EnableTool(GBT_MENU_VIEW_ZOOMOUT, !p_event.IsChecked());
GetToolBar()->EnableTool(GBT_MENU_VIEW_ZOOMFIT, !p_event.IsChecked());

OnUpdate();
}

//----------------------------------------------------------------------
Expand Down
Loading