Open dcnieho opened 1 year ago
Ah, i see you have globals.settings.interface_scaling
that would do the trick here. It is unclear to me however where you read the value from the db, i only see various places where its updated.
It is unclear to me however where you read the value from the db, i only see various places where its updated.
Here, the db returns a sqlite3.Row
item (because I told it to), which can be converted to a dict
that will be in format {"column_name": "value"}
for each row. In my case the settings
table is a single row, so a single db fetch and a single row converted gets me a simple dict with {"setting_1": "value_1", "setting_2": "value_2"...}
. This is then passed to the settings dataclass: Settings(**settings)
, so each of the dict keys will go to fill an argument in the Settings
constructor. Settings
is a dataclass that has the same property names as the db settings columns, so it is 1:1. I made it like this so it is easily scalable, I just add the column definition in the database, and add its equivalent to the dataclass. Also my helper function for creating the table handles adding missing or edited columns in case I change the table definition throughout the updates.
Ah, i see you have
globals.settings.interface_scaling
that would do the trick here.
Yes that is how I do it currently, but this way it is left handled completely by the user. Maybe having it autodetect a more appropriate default value, then letting the user control it, might be better. I'll look into the snippet you sent and see what could be done.
PS: also as a sidenote there's a code block format available for diff files:
```diff
stuff here
```
diff --git a/modules/gui.py b/modules/gui.py
index 244539c..f57e020 100644
--- a/modules/gui.py
+++ b/modules/gui.py
@@ -147,6 +147,13 @@ class MainGUI():
self.screen_pos = glfw.get_window_pos(self.window)
if globals.settings.start_in_tray:
self.minimize()
+
+ highDPIscaleFactor = 1.0
+ xscale,yscale = glfw.get_monitor_content_scale(glfw.get_primary_monitor())
+ if xscale > 1 or yscale > 1:
+ highDPIscaleFactor = xscale
+ glfw.window_hint(glfw.SCALE_TO_MONITOR, gl.GL_TRUE)
+
icon_path = globals.self_path / "resources/icons/icon.png"
self.icon_texture = imagehelper.ImageHelper(icon_path)
glfw.set_window_icon(self.window, 1, Image.open(icon_path))
@@ -188,6 +195,8 @@ class MainGUI():
imgui.style.frame_border_size = 1.6
imgui.style.colors[imgui.COLOR_TABLE_BORDER_STRONG] = (0, 0, 0, 0)
self.refresh_styles()
+ if highDPIscaleFactor > 1.0:
+ imgui.style.scale_all_sizes(highDPIscaleFactor)
# Custom checkbox style
imgui._checkbox = imgui.checkbox
def checkbox(label: str, state: bool):
@Willy-JL Thanks for the explanation, thats a very clean way of doing it. I was planning to go with db, but realize now its a clean way of persisting info across runs and indeed a nice way of setting things up. Thanks also for the pointer about the diff highlighter for code blocks, i wasn't aware.
I guess imgui.style.scale_all_sizes, even if implemented, is very much what you do not want to do. Somehow setting globals.settings.interface_scaling
on first run to a better default, e.g. a value gotten using glfw.get_monitor_content_scale()
might be better.
Hmm, and note that there was something stupid in the above quick diff: glfw.window_hint(glfw.SCALE_TO_MONITOR, gl.GL_TRUE)
should of course be issued before opening the window. That takes care of scaling the main window size on first run.
Still untested, but i have written the following to get the current monitor that a screen is on:
def get_current_monitor(wx, wy, ww, wh):
import ctypes
# so we always return something sensible
monitor = glfw.get_primary_monitor()
bestoverlap = 0
for mon in glfw.get_monitors():
monitor_area = glfw.get_monitor_workarea(mon)
mx, my = monitor_area[0], monitor_area[1]
mw, mh = monitor_area[2], monitor_area[3]
overlap = \
max(0, min(wx + ww, mx + mw) - max(wx, mx)) * \
max(0, min(wy + wh, my + mh) - max(wy, my))
if bestoverlap < overlap:
bestoverlap = overlap
monitor = mon
return monitor, ctypes.cast(ctypes.pointer(monitor), ctypes.POINTER(ctypes.c_long)).contents.value
The first return argument can then be used to determine scaling of the display:
xscale, yscale = glfw.get_monitor_content_scale(mon)
self.size_mult = max(xscale, yscale)
and the second return to keep track of whether a position change entailed moving to another monitor.
The ctypes stuff is a little ugly, but the alternative would be to use monitor user data to identify monitors uniquely, which would necessitate a new monitor callback. And thats broken in the glfw wrapper: https://github.com/FlorianRhiem/pyGLFW/issues/66
My logic here is that i will not make scaling configurable, but simply autodetected like this. May be seen as a loss of functionality for some, but should do the trick for me
Ok, taking the approach of taking scaling control away from the user, i've got this figured out. It works 1. when dragging between monitors with different DPI scaling, and 2. when starting app on a monitor with a different scaling than where it was last closed.
Here's the parts. imgui setup in gui.py:
size = tuple()
pos = tuple()
old_scale = 1.
is_default = False
try:
# Get window size
with open(imgui.io.ini_file_name, "r") as f:
ini = f.read()
imgui.load_ini_settings_from_memory(ini)
config = configparser.RawConfigParser()
config.optionxform=str
config.read_string(ini)
try:
size = tuple(int(x) for x in config["Window][glassesValidator"]["Size"].split(","))
except Exception:
pass
try:
pos = tuple(int(x) for x in config["Window][glassesValidator"]["ScreenPos"].split(","))
except Exception:
pass
try:
old_scale = config.getfloat("Window][glassesValidator","Scale",1.)
except Exception:
pass
except Exception:
pass
if not all([isinstance(x, int) for x in size]) or not len(size) == 2:
size = (1280, 720)
is_default = True
# Setup GLFW window
self.window: glfw._GLFWwindow = utils.impl_glfw_init(*size, "glassesValidator")
if all([isinstance(x, int) for x in pos]) and len(pos) == 2 and utils.validate_geometry(*pos, *size):
glfw.set_window_pos(self.window, *pos)
self.screen_pos = glfw.get_window_pos(self.window)
self.screen_size= glfw.get_window_size(self.window)
# Determine what monitor we're (mostly) on, for scaling
mon, self.monitor = utils.get_current_monitor(*self.screen_pos, *self.screen_size)
# apply scaling
xscale, yscale = glfw.get_monitor_content_scale(mon)
self.size_mult = max(xscale, yscale)
if is_default:
glfw.set_window_size(self.window, int(self.screen_size[0]*self.size_mult), int(self.screen_size[1]*self.size_mult))
elif self.size_mult is not old_scale:
glfw.set_window_size(self.window, int(self.screen_size[0]/old_scale*self.size_mult), int(self.screen_size[1]/old_scale*self.size_mult))
self.last_size_mult = self.size_mult
add the below to pos_callback
:
# check if we moved to another monitor
mon, mon_id = utils.get_current_monitor(*self.screen_pos, *self.screen_size)
if mon_id != self.monitor:
self.monitor = mon_id
# update scaling
xscale, yscale = glfw.get_monitor_content_scale(mon)
if scale := max(xscale, yscale):
self.size_mult = scale
# resize window if needed
if self.size_mult != self.last_size_mult:
self.new_screen_size = int(self.screen_size[0]/self.last_size_mult*self.size_mult), int(self.screen_size[1]/self.last_size_mult*self.size_mult)
(NB: apparently we can't call glfw.set_window_size()
from inside the poss callback, it got ignored. Therefore, in main loop:
if self.repeat_chars:
for char in self.input_chars:
imgui.io.add_input_character(char)
self.repeat_chars = False
self.input_chars.clear()
glfw.poll_events()
self.impl.process_inputs()
+ # if there's a queued window resize, execute
+ if self.new_screen_size[0]!=0 and self.new_screen_size!=self.screen_size:
+ glfw.set_window_size(self.window, *self.new_screen_size)
+ glfw.poll_events()
if not self.focused and glfw.get_window_attrib(self.window, glfw.HOVERED):
at the end of the main loop, the check is changed to:
if self.size_mult != self.last_size_mult:
self.refresh_fonts()
self.refresh_styles()
self.last_size_mult = self.size_mult
save last scale to imgui ini file (by the way, saving code much simplified, can use configparser for that):
def save_imgui_ini(self, path: str | pathlib.Path = None):
if path is None:
path = imgui.io.ini_file_name
imgui.save_ini_settings_to_disk(str(path))
ini = imgui.save_ini_settings_to_memory()
# add some of our own stuff we want to persist
try:
config = configparser.RawConfigParser()
config.optionxform=str
config.read_string(ini)
config["Window][glassesValidator"]["ScreenPos"] = f"{self.screen_pos[0]},{self.screen_pos[1]}"
config["Window][glassesValidator"]["Scale"] = f"{self.size_mult}"
with open(str(path), "w") as f:
config.write(f)
except Exception:
pass # already saved with imgui.save_ini_settings_to_disk above
and last the function in utils to check what monitor we're on:
def get_current_monitor(wx, wy, ww, wh):
import ctypes
# so we always return something sensible
monitor = glfw.get_primary_monitor()
bestoverlap = 0
for mon in glfw.get_monitors():
monitor_area = glfw.get_monitor_workarea(mon)
mx, my = monitor_area[0], monitor_area[1]
mw, mh = monitor_area[2], monitor_area[3]
overlap = \
max(0, min(wx + ww, mx + mw) - max(wx, mx)) * \
max(0, min(wy + wh, my + mh) - max(wy, my))
if bestoverlap < overlap:
bestoverlap = overlap
monitor = mon
return monitor, ctypes.cast(ctypes.pointer(monitor), ctypes.POINTER(ctypes.c_long)).contents.value
the glfw wrapper is now fixed so that set_monitor_user_pointer
and get_monitor_user_pointer
now work so perhaps that ctype stuff can be replaced, will look at it later
I found your post about this program in the pyimgui issues, and want to use it as a basis for developing my own GUI (for a very different program :p).
I am on Windows 10, and have a High DPI screen (scale factor 2.5). So the interface is tiny. I have previously worked with imgui in C++ where one could set a global scaling. It seems the version exposed by pyimgui is too old (or that particular function not wrapped). If it would be available, something like the below patch should do the trick.
imgui.style.scale_all_sizes()
doesn't exist. I'm posting this here for when it becomes available.Thanks for the great work and highlighting it in the pyimgui forums so that i could find it. I look forward designing my program with your code as an example