WX: HiDPI Support Framework
Portable flexible HiDPI image loading and other support for Windows/GTK/OSX.
This commit is contained in:
parent
9df8388900
commit
73a20551df
|
@ -40,6 +40,7 @@ set(GUI_SRCS
|
||||||
NetPlay/NetPlaySetupFrame.cpp
|
NetPlay/NetPlaySetupFrame.cpp
|
||||||
NetPlay/NetWindow.cpp
|
NetPlay/NetWindow.cpp
|
||||||
NetPlay/PadMapDialog.cpp
|
NetPlay/PadMapDialog.cpp
|
||||||
|
DolphinSlider.cpp
|
||||||
FifoPlayerDlg.cpp
|
FifoPlayerDlg.cpp
|
||||||
Frame.cpp
|
Frame.cpp
|
||||||
FrameAui.cpp
|
FrameAui.cpp
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
// Copyright 2016 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <wx/utils.h>
|
||||||
|
|
||||||
|
#include "DolphinWX/DolphinSlider.h"
|
||||||
|
|
||||||
|
#ifdef __WXMSW__
|
||||||
|
#define WIN32_LEAN_AND_MEAN 1
|
||||||
|
// clang-format off
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <CommCtrl.h>
|
||||||
|
// clang-format on
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static constexpr int SLIDER_MIN_LENGTH = 100;
|
||||||
|
|
||||||
|
DolphinSlider::DolphinSlider() = default;
|
||||||
|
DolphinSlider::~DolphinSlider() = default;
|
||||||
|
|
||||||
|
bool DolphinSlider::Create(wxWindow* parent, wxWindowID id, int value, int min_val, int max_val,
|
||||||
|
const wxPoint& pos, const wxSize& size, long style,
|
||||||
|
const wxValidator& validator, const wxString& name)
|
||||||
|
{
|
||||||
|
// Sanitize the style flags.
|
||||||
|
// We don't want any label flags because those break DPI scaling on wxMSW,
|
||||||
|
// wxWidgets will internally lock the height of the slider to 32 pixels.
|
||||||
|
style &= ~wxSL_LABELS;
|
||||||
|
|
||||||
|
return wxSlider::Create(parent, id, value, min_val, max_val, pos, size, style, validator, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSize DolphinSlider::DoGetBestClientSize() const
|
||||||
|
{
|
||||||
|
#ifdef __WXMSW__
|
||||||
|
int ticks = 0;
|
||||||
|
int default_length = FromDIP(SLIDER_MIN_LENGTH);
|
||||||
|
if (HasFlag(wxSL_TICKS))
|
||||||
|
{
|
||||||
|
// NOTE: Ticks do not scale at all (on Win7)
|
||||||
|
default_length += 4;
|
||||||
|
ticks = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
int metric = 0;
|
||||||
|
{
|
||||||
|
// We need to determine the maximum thumb size because unfortunately the thumb size
|
||||||
|
// is controlled by the theme so may have a varying maximum size limit.
|
||||||
|
// NOTE: We can't use ourself because we're const so we can't change our size.
|
||||||
|
// NOTE: This is less inefficient then it seems, DoGetBestSize() is only called once
|
||||||
|
// per instance and cached until InvalidateBestSize() is called.
|
||||||
|
wxSlider* helper = new wxSlider(GetParent(), wxID_ANY, GetValue(), GetMin(), GetMax(),
|
||||||
|
wxDefaultPosition, FromDIP(wxSize(100, 100)), GetWindowStyle());
|
||||||
|
::RECT r{};
|
||||||
|
::SendMessageW(reinterpret_cast<HWND>(helper->GetHWND()), TBM_GETTHUMBRECT, 0,
|
||||||
|
reinterpret_cast<LPARAM>(&r));
|
||||||
|
helper->Destroy();
|
||||||
|
|
||||||
|
// Breakdown metrics
|
||||||
|
int computed_size;
|
||||||
|
int scroll_size;
|
||||||
|
if (HasFlag(wxSL_VERTICAL))
|
||||||
|
{
|
||||||
|
// Trackbar thumb does not directly touch the edge, we add the padding
|
||||||
|
// a second time to pad the other edge to make it symmetric.
|
||||||
|
computed_size = static_cast<int>(r.right + r.left);
|
||||||
|
scroll_size = ::GetSystemMetrics(SM_CXVSCROLL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
computed_size = static_cast<int>(r.bottom + r.top);
|
||||||
|
scroll_size = ::GetSystemMetrics(SM_CYHSCROLL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is based on how Microsoft calculates trackbar sizes in the .Net Framework
|
||||||
|
// when using automatic sizing in WinForms.
|
||||||
|
int max = scroll_size * 2;
|
||||||
|
|
||||||
|
metric = wxClip(computed_size, scroll_size, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasFlag(wxSL_VERTICAL))
|
||||||
|
return wxSize(metric + ticks, default_length);
|
||||||
|
return wxSize(default_length, metric + ticks);
|
||||||
|
#else
|
||||||
|
wxSize base_size = wxSlider::DoGetBestClientSize();
|
||||||
|
// If the base class is not using DoGetBestClientSize(), fallback to DoGetBestSize()
|
||||||
|
if (base_size == wxDefaultSize)
|
||||||
|
return wxDefaultSize;
|
||||||
|
return CorrectMinSize(base_size);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSize DolphinSlider::DoGetBestSize() const
|
||||||
|
{
|
||||||
|
return CorrectMinSize(wxSlider::DoGetBestSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSize DolphinSlider::CorrectMinSize(wxSize size) const
|
||||||
|
{
|
||||||
|
wxSize default_length = FromDIP(wxSize(SLIDER_MIN_LENGTH, SLIDER_MIN_LENGTH));
|
||||||
|
// GTK Styles sometimes don't return a default length.
|
||||||
|
// NOTE: Vertical is the dominant flag if both are set.
|
||||||
|
if (HasFlag(wxSL_VERTICAL))
|
||||||
|
{
|
||||||
|
if (size.GetHeight() < default_length.GetHeight())
|
||||||
|
{
|
||||||
|
size.SetHeight(default_length.GetHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (size.GetWidth() < default_length.GetWidth())
|
||||||
|
{
|
||||||
|
size.SetWidth(default_length.GetWidth());
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __WXMSW__
|
||||||
|
WXLRESULT DolphinSlider::MSWWindowProc(WXUINT msg, WXWPARAM wp, WXLPARAM lp)
|
||||||
|
{
|
||||||
|
if (msg == WM_THEMECHANGED)
|
||||||
|
InvalidateBestSize();
|
||||||
|
return wxSlider::MSWWindowProc(msg, wp, lp);
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2016 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <wx/slider.h>
|
||||||
|
|
||||||
|
// wxSlider has several bugs, including not scaling with DPI.
|
||||||
|
// This extended slider class tries to paper over the flaws.
|
||||||
|
// NOTE: wxSL_LABELS is not supported because it doesn't work correctly.
|
||||||
|
class DolphinSlider : public wxSlider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DolphinSlider();
|
||||||
|
~DolphinSlider() override;
|
||||||
|
DolphinSlider(const DolphinSlider&) = delete;
|
||||||
|
|
||||||
|
DolphinSlider(wxWindow* parent, wxWindowID id, int value, int min_value, int max_value,
|
||||||
|
const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
|
||||||
|
long style = wxSL_HORIZONTAL, const wxValidator& validator = wxDefaultValidator,
|
||||||
|
const wxString& name = wxSliderNameStr)
|
||||||
|
{
|
||||||
|
Create(parent, id, value, min_value, max_value, pos, size, style, validator, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
DolphinSlider& operator=(const DolphinSlider&) = delete;
|
||||||
|
|
||||||
|
bool Create(wxWindow* parent, wxWindowID id, int value, int min_value, int max_value,
|
||||||
|
const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
|
||||||
|
long style = wxSL_HORIZONTAL, const wxValidator& validator = wxDefaultValidator,
|
||||||
|
const wxString& name = wxSliderNameStr);
|
||||||
|
|
||||||
|
#ifdef __WXMSW__
|
||||||
|
// For WM_THEMECHANGED to regenerate metrics
|
||||||
|
WXLRESULT MSWWindowProc(WXUINT msg, WXWPARAM wp, WXLPARAM lp) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// DoGetBestSize() in wxMSW::wxSlider is borked.
|
||||||
|
// This is called by GetEffectiveMinSize() which is used by
|
||||||
|
// wxSizers to decide the size of the widget for generating layout.
|
||||||
|
wxSize DoGetBestClientSize() const override;
|
||||||
|
|
||||||
|
// GTK Themes sometimes don't provide a default min size.
|
||||||
|
// Make other platforms consistent with Windows (i.e. min length = 100px)
|
||||||
|
wxSize DoGetBestSize() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
wxSize CorrectMinSize(wxSize size) const;
|
||||||
|
};
|
|
@ -89,6 +89,7 @@
|
||||||
<ClCompile Include="Debugger\RegisterWindow.cpp" />
|
<ClCompile Include="Debugger\RegisterWindow.cpp" />
|
||||||
<ClCompile Include="Debugger\WatchView.cpp" />
|
<ClCompile Include="Debugger\WatchView.cpp" />
|
||||||
<ClCompile Include="Debugger\WatchWindow.cpp" />
|
<ClCompile Include="Debugger\WatchWindow.cpp" />
|
||||||
|
<ClCompile Include="DolphinSlider.cpp" />
|
||||||
<ClCompile Include="NetPlay\ChangeGameDialog.cpp" />
|
<ClCompile Include="NetPlay\ChangeGameDialog.cpp" />
|
||||||
<ClCompile Include="NetPlay\MD5Dialog.cpp" />
|
<ClCompile Include="NetPlay\MD5Dialog.cpp" />
|
||||||
<ClCompile Include="NetPlay\NetPlayLauncher.cpp" />
|
<ClCompile Include="NetPlay\NetPlayLauncher.cpp" />
|
||||||
|
@ -131,6 +132,7 @@
|
||||||
<ClInclude Include="Config\InterfaceConfigPane.h" />
|
<ClInclude Include="Config\InterfaceConfigPane.h" />
|
||||||
<ClInclude Include="Config\PathConfigPane.h" />
|
<ClInclude Include="Config\PathConfigPane.h" />
|
||||||
<ClInclude Include="Config\WiiConfigPane.h" />
|
<ClInclude Include="Config\WiiConfigPane.h" />
|
||||||
|
<ClInclude Include="DolphinSlider.h" />
|
||||||
<ClInclude Include="NetPlay\ChangeGameDialog.h" />
|
<ClInclude Include="NetPlay\ChangeGameDialog.h" />
|
||||||
<ClInclude Include="NetPlay\MD5Dialog.h" />
|
<ClInclude Include="NetPlay\MD5Dialog.h" />
|
||||||
<ClInclude Include="NetPlay\NetPlayLauncher.h" />
|
<ClInclude Include="NetPlay\NetPlayLauncher.h" />
|
||||||
|
|
|
@ -28,6 +28,9 @@
|
||||||
<Filter Include="GUI\Config">
|
<Filter Include="GUI\Config">
|
||||||
<UniqueIdentifier>{9d8b4144-f335-4fa4-b995-852533298474}</UniqueIdentifier>
|
<UniqueIdentifier>{9d8b4144-f335-4fa4-b995-852533298474}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
|
<Filter Include="GUI\Widgets">
|
||||||
|
<UniqueIdentifier>{a894e2e3-e577-4b65-8572-055699f23a49}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="Main.cpp" />
|
<ClCompile Include="Main.cpp" />
|
||||||
|
@ -208,6 +211,9 @@
|
||||||
<ClCompile Include="NetPlay\NetPlayLauncher.cpp">
|
<ClCompile Include="NetPlay\NetPlayLauncher.cpp">
|
||||||
<Filter>GUI\NetPlay</Filter>
|
<Filter>GUI\NetPlay</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="DolphinSlider.cpp">
|
||||||
|
<Filter>GUI\Widgets</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="Main.h" />
|
<ClInclude Include="Main.h" />
|
||||||
|
@ -381,6 +387,9 @@
|
||||||
<ClInclude Include="NetPlay\NetPlayLauncher.h">
|
<ClInclude Include="NetPlay\NetPlayLauncher.h">
|
||||||
<Filter>GUI\NetPlay</Filter>
|
<Filter>GUI\NetPlay</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="DolphinSlider.h">
|
||||||
|
<Filter>GUI\Widgets</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Text Include="CMakeLists.txt" />
|
<Text Include="CMakeLists.txt" />
|
||||||
|
@ -393,4 +402,4 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="$(CoreDir)..\..\Installer\Dolphin.ico" />
|
<Image Include="$(CoreDir)..\..\Installer\Dolphin.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -2,18 +2,29 @@
|
||||||
// Licensed under GPLv2+
|
// Licensed under GPLv2+
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <wx/app.h>
|
#include <wx/app.h>
|
||||||
#include <wx/bitmap.h>
|
#include <wx/bitmap.h>
|
||||||
|
#include <wx/choice.h>
|
||||||
|
#include <wx/combo.h>
|
||||||
|
#include <wx/combobox.h>
|
||||||
|
#include <wx/display.h>
|
||||||
#include <wx/gdicmn.h>
|
#include <wx/gdicmn.h>
|
||||||
#include <wx/image.h>
|
#include <wx/image.h>
|
||||||
#include <wx/msgdlg.h>
|
#include <wx/msgdlg.h>
|
||||||
#include <wx/mstream.h>
|
#include <wx/sizer.h>
|
||||||
|
#include <wx/spinctrl.h>
|
||||||
#include <wx/toolbar.h>
|
#include <wx/toolbar.h>
|
||||||
|
#include <wx/toplevel.h>
|
||||||
#include <wx/utils.h>
|
#include <wx/utils.h>
|
||||||
|
|
||||||
#include "Common/CommonPaths.h"
|
#include "Common/CommonPaths.h"
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
|
#include "Common/StringUtil.h"
|
||||||
|
#include "Core/ConfigManager.h"
|
||||||
|
|
||||||
#include "DolphinWX/WxUtils.h"
|
#include "DolphinWX/WxUtils.h"
|
||||||
|
|
||||||
|
@ -57,43 +68,15 @@ void ShowErrorDialog(const wxString& error_msg)
|
||||||
|
|
||||||
wxBitmap LoadResourceBitmap(const std::string& name, const wxSize& padded_size)
|
wxBitmap LoadResourceBitmap(const std::string& name, const wxSize& padded_size)
|
||||||
{
|
{
|
||||||
const std::string path_base = File::GetSysDirectory() + RESOURCES_DIR + DIR_SEP + name;
|
wxWindow* context = wxTheApp->GetTopWindow();
|
||||||
std::string path = path_base + ".png";
|
return LoadScaledResourceBitmap(name, context, padded_size, wxDefaultSize,
|
||||||
double scale_factor = 1.0;
|
LSI_SCALE_DOWN | LSI_ALIGN_VCENTER, *wxWHITE);
|
||||||
#ifdef __APPLE__
|
|
||||||
if (wxTheApp->GetTopWindow()->GetContentScaleFactor() >= 2)
|
|
||||||
{
|
|
||||||
const std::string path_2x = path_base + "@2x.png";
|
|
||||||
if (File::Exists(path_2x))
|
|
||||||
{
|
|
||||||
path = path_2x;
|
|
||||||
scale_factor = 2.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
wxImage image(StrToWxStr(path), wxBITMAP_TYPE_PNG);
|
|
||||||
|
|
||||||
if (padded_size != wxSize())
|
|
||||||
{
|
|
||||||
// Add padding if necessary (or crop, but images aren't supposed to be large enough to require
|
|
||||||
// that).
|
|
||||||
// The image will be left-aligned and vertically centered.
|
|
||||||
const wxSize scaled_padded_size = padded_size * scale_factor;
|
|
||||||
image.Resize(scaled_padded_size,
|
|
||||||
wxPoint(0, (scaled_padded_size.GetHeight() - image.GetHeight()) / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
return wxBitmap(image, -1, scale_factor);
|
|
||||||
#else
|
|
||||||
return wxBitmap(image);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wxBitmap CreateDisabledButtonBitmap(const wxBitmap& original)
|
wxBitmap CreateDisabledButtonBitmap(const wxBitmap& original)
|
||||||
{
|
{
|
||||||
wxImage image = original.ConvertToImage();
|
wxImage image = original.ConvertToImage();
|
||||||
return wxBitmap(image.ConvertToDisabled(240));
|
return wxBitmap(image.ConvertToDisabled(240), wxBITMAP_SCREEN_DEPTH, original.GetScaleFactor());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddToolbarButton(wxToolBar* toolbar, int toolID, const wxString& label, const wxBitmap& bitmap,
|
void AddToolbarButton(wxToolBar* toolbar, int toolID, const wxString& label, const wxBitmap& bitmap,
|
||||||
|
@ -105,6 +88,396 @@ void AddToolbarButton(wxToolBar* toolbar, int toolID, const wxString& label, con
|
||||||
wxITEM_NORMAL, shortHelp);
|
wxITEM_NORMAL, shortHelp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wxRect GetVirtualScreenGeometry()
|
||||||
|
{
|
||||||
|
wxRect geometry;
|
||||||
|
for (unsigned int i = 0, end = wxDisplay::GetCount(); i < end; ++i)
|
||||||
|
geometry.Union(wxDisplay(i).GetGeometry());
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetWindowSizeAndFitToScreen(wxTopLevelWindow* tlw, wxPoint pos, wxSize size,
|
||||||
|
wxSize default_size)
|
||||||
|
{
|
||||||
|
if (tlw->IsMaximized())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// NOTE: Positions can be negative and still be valid. Coordinates are relative to the
|
||||||
|
// primary monitor so if the primary monitor is in the middle then (-1000, 10) is a
|
||||||
|
// valid position on the monitor to the left of the primary. (This does not apply to
|
||||||
|
// sizes obviously)
|
||||||
|
wxRect screen_geometry;
|
||||||
|
wxRect window_geometry{pos, size};
|
||||||
|
|
||||||
|
if (wxDisplay::GetCount() > 1)
|
||||||
|
screen_geometry = GetVirtualScreenGeometry();
|
||||||
|
else
|
||||||
|
screen_geometry = wxDisplay(0).GetClientArea();
|
||||||
|
|
||||||
|
// Initialize the default size if it is wxDefaultSize or otherwise negative.
|
||||||
|
default_size.DecTo(screen_geometry.GetSize());
|
||||||
|
default_size.IncTo(tlw->GetMinSize());
|
||||||
|
if (!default_size.IsFullySpecified())
|
||||||
|
default_size.SetDefaults(wxDisplay(0).GetClientArea().GetSize() / 2);
|
||||||
|
|
||||||
|
// If the position we're given doesn't make sense then go with the current position.
|
||||||
|
// (Assuming the window was created with wxDefaultPosition then this should be reasonable)
|
||||||
|
if (pos.x - screen_geometry.GetLeft() < -1000 || pos.y - screen_geometry.GetTop() < -1000 ||
|
||||||
|
pos.x - screen_geometry.GetRight() > 1000 || pos.y - screen_geometry.GetBottom() > 1000)
|
||||||
|
{
|
||||||
|
window_geometry.SetPosition(tlw->GetPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the window is bigger than all monitors combined, or negative (uninitialized) then reset it.
|
||||||
|
if (window_geometry.IsEmpty() || window_geometry.GetWidth() > screen_geometry.GetWidth() ||
|
||||||
|
window_geometry.GetHeight() > screen_geometry.GetHeight())
|
||||||
|
{
|
||||||
|
window_geometry.SetSize(default_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the window entirely lives on a single monitor without spanning.
|
||||||
|
// If the window does not span multiple screens then we should constrain it within that
|
||||||
|
// single monitor instead of the entire virtual desktop space.
|
||||||
|
// The benefit to doing this is that we can account for the OS X menu bar and Windows task
|
||||||
|
// bar which are treated as invisible when only looking at the virtual desktop instead of
|
||||||
|
// an individual screen.
|
||||||
|
if (wxDisplay::GetCount() > 1)
|
||||||
|
{
|
||||||
|
// SPECIAL CASE: If the window is entirely outside the visible area of the desktop then we
|
||||||
|
// put it back on the primary (zero) monitor.
|
||||||
|
wxRect monitor_intersection{window_geometry};
|
||||||
|
int the_monitor = 0;
|
||||||
|
if (!monitor_intersection.Intersect(screen_geometry).IsEmpty())
|
||||||
|
{
|
||||||
|
std::array<int, 4> monitors{{wxDisplay::GetFromPoint(monitor_intersection.GetTopLeft()),
|
||||||
|
wxDisplay::GetFromPoint(monitor_intersection.GetTopRight()),
|
||||||
|
wxDisplay::GetFromPoint(monitor_intersection.GetBottomLeft()),
|
||||||
|
wxDisplay::GetFromPoint(monitor_intersection.GetBottomRight())}};
|
||||||
|
the_monitor = wxNOT_FOUND;
|
||||||
|
bool intersected = false;
|
||||||
|
for (int one_monitor : monitors)
|
||||||
|
{
|
||||||
|
if (one_monitor == the_monitor || one_monitor == wxNOT_FOUND)
|
||||||
|
continue;
|
||||||
|
if (the_monitor != wxNOT_FOUND)
|
||||||
|
{
|
||||||
|
// The window is spanning multiple screens.
|
||||||
|
the_monitor = wxNOT_FOUND;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
the_monitor = one_monitor;
|
||||||
|
intersected = true;
|
||||||
|
}
|
||||||
|
// If we get wxNOT_FOUND for all corners then there are holes in the virtual desktop and the
|
||||||
|
// entire window is lost in one. (e.g. 3 monitors in an 'L', window in top-right)
|
||||||
|
if (!intersected)
|
||||||
|
the_monitor = 0;
|
||||||
|
}
|
||||||
|
if (the_monitor != wxNOT_FOUND)
|
||||||
|
{
|
||||||
|
// We'll only use the client area of this monitor if the window will actually fit.
|
||||||
|
// (It may not fit if the window is spilling off the edge so it isn't entirely visible)
|
||||||
|
wxRect client_area{wxDisplay(the_monitor).GetClientArea()};
|
||||||
|
if (client_area.GetWidth() >= window_geometry.GetWidth() &&
|
||||||
|
client_area.GetHeight() >= window_geometry.GetHeight())
|
||||||
|
{
|
||||||
|
screen_geometry = client_area;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The window SHOULD be small enough to fit on the screen, but it might be spilling off an edge
|
||||||
|
// so we'll snap it to the nearest edge as necessary.
|
||||||
|
if (!screen_geometry.Contains(window_geometry))
|
||||||
|
{
|
||||||
|
// NOTE: The order is important here, if the window *is* too big to fit then it will snap to
|
||||||
|
// the top-left corner.
|
||||||
|
int spill_x = std::max(0, window_geometry.GetRight() - screen_geometry.GetRight());
|
||||||
|
int spill_y = std::max(0, window_geometry.GetBottom() - screen_geometry.GetBottom());
|
||||||
|
window_geometry.Offset(-spill_x, -spill_y);
|
||||||
|
if (window_geometry.GetTop() < screen_geometry.GetTop())
|
||||||
|
window_geometry.SetTop(screen_geometry.GetTop());
|
||||||
|
if (window_geometry.GetLeft() < screen_geometry.GetLeft())
|
||||||
|
window_geometry.SetLeft(screen_geometry.GetLeft());
|
||||||
|
}
|
||||||
|
|
||||||
|
tlw->SetSize(window_geometry, wxSIZE_ALLOW_MINUS_ONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSizer* GiveMinSize(wxWindow* window, const wxSize& min_size)
|
||||||
|
{
|
||||||
|
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
int flags = wxEXPAND;
|
||||||
|
// On Windows comboboxes will misrender when stretched vertically.
|
||||||
|
if (wxDynamicCast(window, wxChoice) || wxDynamicCast(window, wxComboBox) ||
|
||||||
|
wxDynamicCast(window, wxComboCtrl))
|
||||||
|
flags = wxALIGN_CENTER_VERTICAL;
|
||||||
|
sizer->Add(window, 1, flags);
|
||||||
|
sizer->SetMinSize(min_size);
|
||||||
|
return sizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSizer* GiveMinSizeDIP(wxWindow* window, const wxSize& min_size)
|
||||||
|
{
|
||||||
|
return GiveMinSize(window, window->FromDIP(min_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSize GetTextWidgetMinSize(const wxControl* control, const wxString& value)
|
||||||
|
{
|
||||||
|
return control->GetSizeFromTextSize(control->GetTextExtent(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSize GetTextWidgetMinSize(const wxControl* control, unsigned int value)
|
||||||
|
{
|
||||||
|
return GetTextWidgetMinSize(control, wxString::Format("%u", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSize GetTextWidgetMinSize(const wxControl* control, int value)
|
||||||
|
{
|
||||||
|
return GetTextWidgetMinSize(control, wxString::Format("%d", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSize GetTextWidgetMinSize(const wxSpinCtrl* spinner)
|
||||||
|
{
|
||||||
|
wxSize size = GetTextWidgetMinSize(spinner, spinner->GetMin());
|
||||||
|
size.IncTo(GetTextWidgetMinSize(spinner, spinner->GetMax()));
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static wxImage LoadScaledImage(const std::string& file_path, const wxWindow* context,
|
||||||
|
const wxSize& output_size, const wxRect& usable_rect, LSIFlags flags,
|
||||||
|
const wxColour& fill_color)
|
||||||
|
{
|
||||||
|
std::string fpath, fname, fext;
|
||||||
|
SplitPath(file_path, &fpath, &fname, &fext);
|
||||||
|
|
||||||
|
const double window_scale_factor = context->GetContentScaleFactor();
|
||||||
|
// Compute the total scale factor from the ratio of DIPs to window pixels (FromDIP) and
|
||||||
|
// window pixels to framebuffer pixels (GetContentScaleFactor).
|
||||||
|
// NOTE: Usually only one of these is meaningful:
|
||||||
|
// - On Windows/GTK2: content_scale = 1.0, FromDIP = 96DPI -> Screen DPI
|
||||||
|
// - On Mac OS X: content_scale = screen_dpi / 96, FromDIP = 96DPI -> 96DPI (no-op)
|
||||||
|
// [The 1024 is arbitrarily large to minimise rounding error, it has no significance]
|
||||||
|
const double scale_factor = (context->FromDIP(1024) / 1024.0) * window_scale_factor;
|
||||||
|
|
||||||
|
// We search for files on quarter ratios of DIPs to framebuffer pixels.
|
||||||
|
// By default, the algorithm prefers to find an exact or bigger size then downscale if
|
||||||
|
// needed but will resort to upscaling if a bigger image cannot be found.
|
||||||
|
// E.g. A basic retina screen on Mac OS X has a scale_factor of 2.0, so we would look for
|
||||||
|
// @2x, @2.25x, @2.5x, @2.75x, @3x, @1.75x, @1.5x, @1.25x, @1x, then give up.
|
||||||
|
// (At 125% on Windows the search is @1.25, @1.5, @1.75, @2, @2.25, @1)
|
||||||
|
// If flags does not include LSI_SCALE_DOWN (i.e. we would be forced to crop big
|
||||||
|
// images instead of scaling them) then we will only accept smaller sizes, i.e.
|
||||||
|
// @2x, @1.75, @1.5, @1.25, @1, then give up.
|
||||||
|
// NOTE: We do a lot of exact comparisons against floating point here but it's fine
|
||||||
|
// because the numbers involved are all powers of 2 so can be represented exactly.
|
||||||
|
wxImage image;
|
||||||
|
double selected_image_scale = 1;
|
||||||
|
{
|
||||||
|
auto image_check = [&](double scale) -> bool {
|
||||||
|
std::string path = fpath + fname + StringFromFormat("@%gx", scale) + fext;
|
||||||
|
if (!File::Exists(path))
|
||||||
|
{
|
||||||
|
// Special Case: @1x may not have a suffix at all.
|
||||||
|
if (scale != 1.0 || !File::Exists(file_path))
|
||||||
|
return false;
|
||||||
|
path = file_path;
|
||||||
|
}
|
||||||
|
if (!image.LoadFile(StrToWxStr(path), wxBITMAP_TYPE_ANY))
|
||||||
|
return false;
|
||||||
|
selected_image_scale = scale;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const bool prefer_smaller = !(flags & LSI_SCALE_DOWN);
|
||||||
|
const double scale_factor_quarter =
|
||||||
|
prefer_smaller ? std::floor(scale_factor * 4) / 4 : std::ceil(scale_factor * 4) / 4;
|
||||||
|
// Search for bigger sizes first (preferred)
|
||||||
|
if (!prefer_smaller)
|
||||||
|
{
|
||||||
|
// We search within a 'circle' of the exact match limited by scale=1.0.
|
||||||
|
// i.e. scale_factor = 1.5, radius = 0.5; scale = 2.5, radius = 1.5.
|
||||||
|
// The minimum radius is 1.0.
|
||||||
|
double limit = std::max(scale_factor_quarter * 2 - 1, scale_factor_quarter + 1);
|
||||||
|
for (double quarter = scale_factor_quarter; quarter <= limit; quarter += 0.25)
|
||||||
|
{
|
||||||
|
if (image_check(quarter))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we didn't hit a bigger size then we'll fallback to looking for smaller ones
|
||||||
|
if (!image.IsOk())
|
||||||
|
{
|
||||||
|
double quarter = scale_factor_quarter;
|
||||||
|
if (!prefer_smaller) // So we don't recheck the exact match
|
||||||
|
quarter -= 0.25;
|
||||||
|
for (; quarter >= 1.0; quarter -= 0.25)
|
||||||
|
{
|
||||||
|
if (image_check(quarter))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The file apparently does not exist so we give up. Create a white square placeholder instead.
|
||||||
|
if (!image.IsOk())
|
||||||
|
{
|
||||||
|
wxLogError("Could not find resource: %s", StrToWxStr(file_path));
|
||||||
|
image.Create(1, 1, false);
|
||||||
|
image.Clear(0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScaleImage(image, selected_image_scale, window_scale_factor, output_size, usable_rect,
|
||||||
|
flags, fill_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxBitmap LoadScaledBitmap(const std::string& file_path, const wxWindow* context,
|
||||||
|
const wxSize& output_size, const wxRect& usable_rect, LSIFlags flags,
|
||||||
|
const wxColour& fill_color)
|
||||||
|
{
|
||||||
|
return wxBitmap(LoadScaledImage(file_path, context, output_size, usable_rect, flags, fill_color),
|
||||||
|
wxBITMAP_SCREEN_DEPTH, context->GetContentScaleFactor());
|
||||||
|
}
|
||||||
|
|
||||||
|
wxBitmap LoadScaledResourceBitmap(const std::string& name, const wxWindow* context,
|
||||||
|
const wxSize& output_size, const wxRect& usable_rect,
|
||||||
|
LSIFlags flags, const wxColour& fill_color)
|
||||||
|
{
|
||||||
|
std::string path = File::GetSysDirectory() + RESOURCES_DIR DIR_SEP + name + ".png";
|
||||||
|
return LoadScaledBitmap(path, context, output_size, usable_rect, flags, fill_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxBitmap LoadScaledThemeBitmap(const std::string& name, const wxWindow* context,
|
||||||
|
const wxSize& output_size, const wxRect& usable_rect, LSIFlags flags,
|
||||||
|
const wxColour& fill_color)
|
||||||
|
{
|
||||||
|
std::string path = File::GetThemeDir(SConfig::GetInstance().theme_name) + name + ".png";
|
||||||
|
return LoadScaledBitmap(path, context, output_size, usable_rect, flags, fill_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxBitmap ScaleImageToBitmap(const wxImage& image, const wxWindow* context,
|
||||||
|
const wxSize& output_size, const wxRect& usable_rect, LSIFlags flags,
|
||||||
|
const wxColour& fill_color)
|
||||||
|
{
|
||||||
|
double scale_factor = context->GetContentScaleFactor();
|
||||||
|
return wxBitmap(ScaleImage(image, 1.0, scale_factor, output_size, usable_rect, flags, fill_color),
|
||||||
|
wxBITMAP_SCREEN_DEPTH, scale_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxBitmap ScaleImageToBitmap(const wxImage& image, const wxWindow* context, double source_scale,
|
||||||
|
LSIFlags flags, const wxColour& fill_color)
|
||||||
|
{
|
||||||
|
double scale_factor = context->GetContentScaleFactor();
|
||||||
|
return wxBitmap(ScaleImage(image, source_scale, scale_factor, wxDefaultSize, wxDefaultSize, flags,
|
||||||
|
fill_color),
|
||||||
|
wxBITMAP_SCREEN_DEPTH, scale_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxImage ScaleImage(wxImage image, double source_scale_factor, double content_scale_factor,
|
||||||
|
wxSize output_size, wxRect usable_rect, LSIFlags flags,
|
||||||
|
const wxColour& fill_color)
|
||||||
|
{
|
||||||
|
if (!image.IsOk())
|
||||||
|
{
|
||||||
|
wxFAIL_MSG("WxUtils::ScaleImage expects a valid image.");
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content_scale_factor != 1.0)
|
||||||
|
{
|
||||||
|
output_size *= content_scale_factor;
|
||||||
|
usable_rect.SetPosition(usable_rect.GetPosition() * content_scale_factor);
|
||||||
|
usable_rect.SetSize(usable_rect.GetSize() * content_scale_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix the output size if it's unset.
|
||||||
|
wxSize img_size = image.GetSize();
|
||||||
|
if (output_size.GetWidth() < 1)
|
||||||
|
output_size.SetWidth(
|
||||||
|
static_cast<int>(img_size.GetWidth() * (content_scale_factor / source_scale_factor)));
|
||||||
|
if (output_size.GetHeight() < 1)
|
||||||
|
output_size.SetHeight(
|
||||||
|
static_cast<int>(img_size.GetHeight() * (content_scale_factor / source_scale_factor)));
|
||||||
|
|
||||||
|
// Fix the usable rect. If it's empty then the whole canvas is usable.
|
||||||
|
if (usable_rect.IsEmpty())
|
||||||
|
{
|
||||||
|
// Constructs a temp wxRect 0,0->output_size then move assigns it.
|
||||||
|
usable_rect = output_size;
|
||||||
|
}
|
||||||
|
else if (!usable_rect.Intersects(output_size))
|
||||||
|
{
|
||||||
|
wxFAIL_MSG("Usable Zone Rectangle is not inside the canvas. Check the output size is correct.");
|
||||||
|
image.Create(1, 1, false);
|
||||||
|
image.SetRGB(0, 0, fill_color.Red(), fill_color.Green(), fill_color.Blue());
|
||||||
|
if (fill_color.Alpha() == wxALPHA_TRANSPARENT)
|
||||||
|
image.SetMaskColour(fill_color.Red(), fill_color.Green(), fill_color.Blue());
|
||||||
|
usable_rect = output_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Scale the image
|
||||||
|
if ((flags & LSI_SCALE) != LSI_SCALE_NONE)
|
||||||
|
{
|
||||||
|
if (flags & LSI_SCALE_NO_ASPECT)
|
||||||
|
{
|
||||||
|
// Stretch scale without preserving the aspect ratio.
|
||||||
|
bool scale_width = (img_size.GetWidth() > usable_rect.GetWidth() && flags & LSI_SCALE_DOWN) ||
|
||||||
|
(img_size.GetWidth() < usable_rect.GetWidth() && flags & LSI_SCALE_UP);
|
||||||
|
bool scale_height =
|
||||||
|
(img_size.GetHeight() > usable_rect.GetHeight() && flags & LSI_SCALE_DOWN) ||
|
||||||
|
(img_size.GetHeight() < usable_rect.GetHeight() && flags & LSI_SCALE_UP);
|
||||||
|
if (scale_width || scale_height)
|
||||||
|
{
|
||||||
|
// NOTE: Using BICUBIC instead of HIGH because it's the same internally
|
||||||
|
// except that downscaling uses a box filter with awful obvious aliasing
|
||||||
|
// for non-integral scale factors.
|
||||||
|
image.Rescale(scale_width ? usable_rect.GetWidth() : img_size.GetWidth(),
|
||||||
|
scale_height ? usable_rect.GetHeight() : img_size.GetHeight(),
|
||||||
|
wxIMAGE_QUALITY_BICUBIC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Scale while preserving the aspect ratio.
|
||||||
|
double scale = std::min(static_cast<double>(usable_rect.GetWidth()) / img_size.GetWidth(),
|
||||||
|
static_cast<double>(usable_rect.GetHeight()) / img_size.GetHeight());
|
||||||
|
int target_width = static_cast<int>(img_size.GetWidth() * scale);
|
||||||
|
int target_height = static_cast<int>(img_size.GetHeight() * scale);
|
||||||
|
// Bilinear produces sharper images when upscaling, bicubic tends to smear/blur sharp edges.
|
||||||
|
if (scale > 1.0 && flags & LSI_SCALE_UP)
|
||||||
|
image.Rescale(target_width, target_height, wxIMAGE_QUALITY_BILINEAR);
|
||||||
|
else if (scale < 1.0 && flags & LSI_SCALE_DOWN)
|
||||||
|
image.Rescale(target_width, target_height, wxIMAGE_QUALITY_BICUBIC);
|
||||||
|
}
|
||||||
|
img_size = image.GetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Resize the canvas to match the output size.
|
||||||
|
// NOTE: If NOT using LSI_SCALE_DOWN then this will implicitly crop the image
|
||||||
|
if (img_size != output_size || usable_rect.GetPosition() != wxPoint())
|
||||||
|
{
|
||||||
|
wxPoint base = usable_rect.GetPosition();
|
||||||
|
if (flags & LSI_ALIGN_HCENTER)
|
||||||
|
base.x += (usable_rect.GetWidth() - img_size.GetWidth()) / 2;
|
||||||
|
else if (flags & LSI_ALIGN_RIGHT)
|
||||||
|
base.x += usable_rect.GetWidth() - img_size.GetWidth();
|
||||||
|
if (flags & LSI_ALIGN_VCENTER)
|
||||||
|
base.y += (usable_rect.GetHeight() - img_size.GetHeight()) / 2;
|
||||||
|
else if (flags & LSI_ALIGN_BOTTOM)
|
||||||
|
base.y += usable_rect.GetHeight() - img_size.GetHeight();
|
||||||
|
|
||||||
|
int r = -1, g = -1, b = -1;
|
||||||
|
if (fill_color.Alpha() != wxALPHA_TRANSPARENT)
|
||||||
|
{
|
||||||
|
r = fill_color.Red();
|
||||||
|
g = fill_color.Green();
|
||||||
|
b = fill_color.Blue();
|
||||||
|
}
|
||||||
|
image.Resize(output_size, base, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::string WxStrToStr(const wxString& str)
|
std::string WxStrToStr(const wxString& str)
|
||||||
|
|
|
@ -5,11 +5,18 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <wx/colour.h>
|
||||||
#include <wx/gdicmn.h>
|
#include <wx/gdicmn.h>
|
||||||
#include <wx/string.h>
|
#include <wx/string.h>
|
||||||
|
|
||||||
|
class wxControl;
|
||||||
class wxBitmap;
|
class wxBitmap;
|
||||||
|
class wxImage;
|
||||||
|
class wxSizer;
|
||||||
|
class wxSpinCtrl;
|
||||||
class wxToolBar;
|
class wxToolBar;
|
||||||
|
class wxTopLevelWindow;
|
||||||
|
class wxWindow;
|
||||||
|
|
||||||
namespace WxUtils
|
namespace WxUtils
|
||||||
{
|
{
|
||||||
|
@ -23,7 +30,7 @@ void Explore(const std::string& path);
|
||||||
void ShowErrorDialog(const wxString& error_msg);
|
void ShowErrorDialog(const wxString& error_msg);
|
||||||
|
|
||||||
// Reads a PNG from the Resources folder
|
// Reads a PNG from the Resources folder
|
||||||
wxBitmap LoadResourceBitmap(const std::string& name, const wxSize& padded_size = wxSize());
|
wxBitmap LoadResourceBitmap(const std::string& name, const wxSize& padded_size = wxDefaultSize);
|
||||||
|
|
||||||
// From a wxBitmap, creates the corresponding disabled version for toolbar buttons
|
// From a wxBitmap, creates the corresponding disabled version for toolbar buttons
|
||||||
wxBitmap CreateDisabledButtonBitmap(const wxBitmap& original);
|
wxBitmap CreateDisabledButtonBitmap(const wxBitmap& original);
|
||||||
|
@ -32,6 +39,108 @@ wxBitmap CreateDisabledButtonBitmap(const wxBitmap& original);
|
||||||
void AddToolbarButton(wxToolBar* toolbar, int toolID, const wxString& label, const wxBitmap& bitmap,
|
void AddToolbarButton(wxToolBar* toolbar, int toolID, const wxString& label, const wxBitmap& bitmap,
|
||||||
const wxString& shortHelp);
|
const wxString& shortHelp);
|
||||||
|
|
||||||
|
// Get the dimensions of the virtual desktop that spans all monitors.
|
||||||
|
// Matches GetSystemMetrics(SM_XVIRTUALSCREEN), etc on Windows.
|
||||||
|
wxRect GetVirtualScreenGeometry();
|
||||||
|
|
||||||
|
// Takes a top-level window and resizes / repositions it so it fits on the screen.
|
||||||
|
// Supports spanning multiple monitors if there are multiple monitors.
|
||||||
|
// Will snap to edge if the window is small enough to fit but spills over the boundary.
|
||||||
|
void SetWindowSizeAndFitToScreen(wxTopLevelWindow* tlw, wxPoint pos, wxSize size,
|
||||||
|
wxSize default_size = wxDefaultSize);
|
||||||
|
|
||||||
|
// wxSizers use the minimum size of a widget when computing layout instead of the best size.
|
||||||
|
// The best size is only taken when the minsize is -1,-1 (i.e. undefined).
|
||||||
|
// This means that elements with a MinSize specified will always have that exact size unless
|
||||||
|
// wxEXPAND-ed.
|
||||||
|
// This problem can be resolved by wrapping the widget in a sizer and setting the minimum size on
|
||||||
|
// the sizer instead. Sizers will always use the best size of the widget, treating their own MinSize
|
||||||
|
// as a floor which is usually what you want.
|
||||||
|
wxSizer* GiveMinSize(wxWindow* window, const wxSize& min_size);
|
||||||
|
wxSizer* GiveMinSizeDIP(wxWindow* window, const wxSize& min_size);
|
||||||
|
|
||||||
|
// Compute the proper size for a text widget (wxTextCtrl, wxChoice, wxSpinCtrl, etc)
|
||||||
|
// Based on the text it will be required to hold. This gives the element the minimum
|
||||||
|
// width to hold the largest text value instead of being arbitrarily wide.
|
||||||
|
wxSize GetTextWidgetMinSize(const wxControl* control, const wxString& value);
|
||||||
|
wxSize GetTextWidgetMinSize(const wxControl* control, unsigned int value);
|
||||||
|
wxSize GetTextWidgetMinSize(const wxControl* control, int value);
|
||||||
|
wxSize GetTextWidgetMinSize(const wxSpinCtrl* spinner);
|
||||||
|
|
||||||
|
enum LSIFlags : unsigned int
|
||||||
|
{
|
||||||
|
LSI_SCALE_NONE = 0, // Disable scaling, only resize canvas
|
||||||
|
LSI_SCALE_UP = 1, // Scale up if needed, but crop instead of scaling down
|
||||||
|
LSI_SCALE_DOWN = 2, // Scale down if needed, only expand canvas instead of scaling up
|
||||||
|
LSI_SCALE = LSI_SCALE_UP | LSI_SCALE_DOWN, // Scale either way as needed.
|
||||||
|
LSI_SCALE_NO_ASPECT = 8, // Disable preserving the aspect ratio of the image.
|
||||||
|
|
||||||
|
LSI_ALIGN_LEFT = 0, // Place image at the left edge of canvas
|
||||||
|
LSI_ALIGN_RIGHT = 0x10, // Place image at the right edge of canvas
|
||||||
|
LSI_ALIGN_HCENTER = 0x20, // Place image in the horizontal center of canvas
|
||||||
|
LSI_ALIGN_TOP = 0, // Place image at the top of the canvas
|
||||||
|
LSI_ALIGN_BOTTOM = 0x40, // Place image at the bottom of the canvas
|
||||||
|
LSI_ALIGN_VCENTER = 0x80, // Place image in the vertical center of canvas
|
||||||
|
|
||||||
|
LSI_ALIGN_CENTER = LSI_ALIGN_HCENTER | LSI_ALIGN_VCENTER,
|
||||||
|
|
||||||
|
LSI_DEFAULT = LSI_SCALE | LSI_ALIGN_CENTER
|
||||||
|
};
|
||||||
|
constexpr LSIFlags operator|(LSIFlags left, LSIFlags right)
|
||||||
|
{
|
||||||
|
return static_cast<LSIFlags>(static_cast<unsigned int>(left) | right);
|
||||||
|
}
|
||||||
|
constexpr LSIFlags operator&(LSIFlags left, LSIFlags right)
|
||||||
|
{
|
||||||
|
return static_cast<LSIFlags>(static_cast<unsigned int>(left) & right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swiss army knife loader function for preparing a scaled resource image file.
|
||||||
|
// Only the path and context are mandatory, other parameters can be ignored.
|
||||||
|
// NOTE: All size parameters are in window pixels, not DIPs or framebuffer pixels.
|
||||||
|
// output_size = size of image canvas if different from native image size. E.g. 96x32
|
||||||
|
// usable_rect = part of image canvas that is considered usable. E.g. 0,0 -> 32,32
|
||||||
|
// Usable zone is helpful if the canvas is bigger than the area which will be drawn on screen.
|
||||||
|
// flags = See LSIFlags
|
||||||
|
// fill_color = Color to fill the unused canvas area (due to aspect ratio or usable_rect).
|
||||||
|
wxBitmap LoadScaledBitmap(const std::string& file_path, const wxWindow* context,
|
||||||
|
const wxSize& output_size = wxDefaultSize,
|
||||||
|
const wxRect& usable_rect = wxDefaultSize, LSIFlags flags = LSI_DEFAULT,
|
||||||
|
const wxColour& fill_color = wxTransparentColour);
|
||||||
|
wxBitmap LoadScaledResourceBitmap(const std::string& name, const wxWindow* context,
|
||||||
|
const wxSize& output_size = wxDefaultSize,
|
||||||
|
const wxRect& usable_rect = wxDefaultSize,
|
||||||
|
LSIFlags flags = LSI_DEFAULT,
|
||||||
|
const wxColour& fill_color = wxTransparentColour);
|
||||||
|
wxBitmap LoadScaledThemeBitmap(const std::string& name, const wxWindow* context,
|
||||||
|
const wxSize& output_size = wxDefaultSize,
|
||||||
|
const wxRect& usable_rect = wxDefaultSize,
|
||||||
|
LSIFlags flags = LSI_DEFAULT,
|
||||||
|
const wxColour& fill_color = wxTransparentColour);
|
||||||
|
|
||||||
|
// Variant of LoadScaledBitmap to scale an image that didn't come from a file.
|
||||||
|
wxBitmap ScaleImageToBitmap(const wxImage& image, const wxWindow* context,
|
||||||
|
const wxSize& output_size = wxDefaultSize,
|
||||||
|
const wxRect& usable_rect = wxDefaultSize, LSIFlags flags = LSI_DEFAULT,
|
||||||
|
const wxColour& fill_color = wxTransparentColour);
|
||||||
|
|
||||||
|
// Rescales image to screen DPI.
|
||||||
|
// "Source scale" is essentially the image's DPI as a ratio to 96DPI, e.g. 144DPI image has a
|
||||||
|
// scale of 1.5.
|
||||||
|
wxBitmap ScaleImageToBitmap(const wxImage& image, const wxWindow* context, double source_scale,
|
||||||
|
LSIFlags flags = LSI_DEFAULT,
|
||||||
|
const wxColour& fill_color = wxTransparentColour);
|
||||||
|
|
||||||
|
// Internal scaling engine behind all the Scaling functions.
|
||||||
|
// Exposes all control parameters instead of infering them from other sources.
|
||||||
|
// "Content scale" is a factor applied to output_size and usable_rect internally to convert them
|
||||||
|
// to framebuffer pixel sizes.
|
||||||
|
// NOTE: Source scale factor only matters if you don't explicitly specify the output size.
|
||||||
|
wxImage ScaleImage(wxImage image, double source_scale_factor = 1.0,
|
||||||
|
double content_scale_factor = 1.0, wxSize output_size = wxDefaultSize,
|
||||||
|
wxRect usable_rect = wxDefaultSize, LSIFlags flags = LSI_DEFAULT,
|
||||||
|
const wxColour& fill_color = wxTransparentColour);
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::string WxStrToStr(const wxString& str);
|
std::string WxStrToStr(const wxString& str);
|
||||||
|
|
Loading…
Reference in New Issue