Skip to content

Commit 9caa3b3

Browse files
committed
New Demo - A template for Rainmater-style Desktop Widgets - has the majority of the features a widget would need is in this template.
1 parent c175416 commit 9caa3b3

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import PySimpleGUI as sg
2+
import sys
3+
import datetime
4+
5+
"""
6+
Desktop Widget - Template to start with
7+
This "template" is meant to give you a starting point towards making your own Desktop Widget
8+
Note - the term "Widget" here means a "Desktop Widget", not a GUI Widget
9+
10+
It has many of the features that a Rainmeter-style Desktop Widget would have
11+
* Save position of window
12+
* Set Alpha channel
13+
* "Edit Me" which will launch your editor to edit the code
14+
* Right click menu to access all setup
15+
* Theme selection
16+
* Preview of window using a different theme
17+
* A command line parm to set the intial position of the window in case one hasn't been saved
18+
* A status section of the window that can be hidden / restored (currently shows last refresh time)
19+
* A title
20+
* A main display area
21+
22+
The contents of your widget may be significantly different than this example. Change the function
23+
make_window to create your own custom layout and window.
24+
25+
There are several important design patterns provided including:
26+
Using a function to define and create your window
27+
Using User Settings APIs to save program settings
28+
A Theme Selection window with previewing capability
29+
30+
The standard PySimpleGUI Coding Conventions are used throughout including
31+
* Naming layout keys in format '-KEY-'
32+
* Naming User Settings keys in the format '-key-'
33+
* Using standard layout, window, event, values variable names
34+
35+
Copyright 2021 PySimpleGUI
36+
"""
37+
38+
ALPHA = 0.9 # Initial alpha until user changes
39+
THEME = 'Dark green 3' # Initial theme until user changes
40+
refresh_font = title_font = 'Courier 8'
41+
main_info_font ='Courier 20'
42+
main_info_size = (10,1)
43+
UPDATE_FREQUENCY_MILLISECONDS = 1000 * 60 * 60 # update every hour by default until set by user
44+
45+
def choose_theme(location, size):
46+
"""
47+
A window to allow new themes to be tried out.
48+
Changes the theme to the newly chosen one and returns theme's name
49+
Automaticallyi switches to new theme and saves the setting in user settings file
50+
51+
:param location: (x,y) location of the Widget's window
52+
:type location: Tuple[int, int]
53+
:param size: Size in pixels of the Widget's window
54+
:type size: Tuple[int, int]
55+
:return: The name of the newly selected theme
56+
:rtype: None | str
57+
"""
58+
layout = [[sg.Text('Try a theme')],
59+
[sg.Listbox(values=sg.theme_list(), size=(20, 20), key='-LIST-', enable_events=True)],
60+
[sg.OK(), sg.Cancel()]]
61+
62+
window = sg.Window('Look and Feel Browser', layout, location=location)
63+
old_theme = sg.theme()
64+
while True: # Event Loop
65+
event, values = window.read()
66+
if event in (sg.WIN_CLOSED, 'Exit', 'OK', 'Cancel'):
67+
break
68+
sg.theme(values['-LIST-'][0])
69+
window.hide()
70+
# make at test window to the left of the current one
71+
test_window = make_window(location=((location[0]-size[0]*1.2, location[1])), test_window=True)
72+
test_window.read(close=True)
73+
if sg.popup_yes_no(f'Do you want to keep {values["-LIST-"]}?', location=location) == 'Yes':
74+
break
75+
window.un_hide()
76+
window.close()
77+
78+
# after choice made, save theme or restore the old one
79+
if event not in ('Cancel', sg.WIN_CLOSED) and values['-LIST-']:
80+
sg.theme(values['-LIST-'][0])
81+
sg.user_settings_set_entry('-theme-', values['-LIST-'][0])
82+
return values['-LIST-'][0]
83+
else:
84+
sg.theme(old_theme)
85+
return None
86+
87+
def make_window(location, test_window=False):
88+
"""
89+
Defines the layout and creates the window for the main window
90+
If the parm test_window is True, then a simplified, and EASY to close version is shown
91+
92+
:param location: (x,y) location to create the window
93+
:type location: Tuple[int, int]
94+
:param test_window: If True, then this is a test window & will close by clicking on it
95+
:type test_window: bool
96+
:return: newly created window
97+
:rtype: sg.Window
98+
"""
99+
title = sg.user_settings_get_entry('-title-', '')
100+
if not test_window:
101+
theme = sg.user_settings_get_entry('-theme-', THEME)
102+
sg.theme(theme)
103+
104+
# ------------------- Window Layout -------------------
105+
106+
if test_window:
107+
title_element = sg.Text('Click to close', font=title_font, enable_events=True)
108+
right_click_menu = [[''], ['Exit',]]
109+
else:
110+
title_element = sg.Text(title, size=(20, 1), font=title_font, justification='c', k='-TITLE-')
111+
right_click_menu = [[''], ['Choose Title', 'Edit Me', 'New Theme', 'Save Location', 'Refresh', 'Set Refresh Rate', 'Show Refresh Info', 'Hide Refresh Info', 'Alpha', [str(x) for x in range(1, 11)], 'Exit', ]]
112+
113+
layout = [[title_element],
114+
[sg.Text('0', size=main_info_size, font=main_info_font, k='-MAIN INFO-', justification='c', enable_events=test_window)],
115+
[sg.pin(sg.Text(size=(15, 2), font=refresh_font, k='-REFRESHED-', justification='c', visible=sg.user_settings_get_entry('-show refresh-', True)))]]
116+
117+
# ------------------- Window Creation -------------------
118+
return sg.Window('Desktop Widget Template', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_justification='c',
119+
element_padding=(0, 0), alpha_channel=sg.user_settings_get_entry('-alpha-', ALPHA), finalize=True, right_click_menu=right_click_menu)
120+
121+
122+
def main(location):
123+
"""
124+
Where execution begins
125+
The Event Loop lives here, but the window creation is done in another function
126+
This is an important design pattern
127+
128+
:param location: Location to create the main window if one is not found in the user settings
129+
:type location: Tuple[int, int]
130+
"""
131+
132+
window = make_window(sg.user_settings_get_entry('-location-', location))
133+
134+
refresh_frequency = sg.user_settings_get_entry('-fresh frequency-', UPDATE_FREQUENCY_MILLISECONDS)
135+
136+
while True: # Event Loop
137+
# Normally a window.read goes here, but first we're updating the values in the window, then reading it
138+
# First update the status information
139+
window['-MAIN INFO-'].update('Your Info')
140+
# for debugging show the last update date time
141+
window['-REFRESHED-'].update(datetime.datetime.now().strftime("%m/%d/%Y\n%I:%M:%S %p"))
142+
143+
# -------------- Start of normal event loop --------------
144+
event, values = window.read(timeout=refresh_frequency)
145+
print(event, values)
146+
if event in (sg.WIN_CLOSED, 'Exit'): # standard exit test... ALWAYS do this
147+
break
148+
if event == 'Edit Me':
149+
sg.execute_editor(__file__)
150+
elif event == 'Choose Title':
151+
new_title = sg.popup_get_text('Choose a title for your Widget', location=window.current_location())
152+
if new_title is not None:
153+
window['-TITLE-'].update(new_title)
154+
sg.user_settings_set_entry('-title-', new_title)
155+
elif event == 'Show Refresh Info':
156+
window['-REFRESHED-'].update(visible=True)
157+
sg.user_settings_set_entry('-show refresh-', True)
158+
elif event == 'Save Location':
159+
sg.user_settings_set_entry('-location-', window.current_location())
160+
elif event == 'Hide Refresh Info':
161+
window['-REFRESHED-'].update(visible=False)
162+
sg.user_settings_set_entry('-show refresh-', False)
163+
elif event in [str(x) for x in range(1, 11)]: # if Alpha Channel was chosen
164+
window.set_alpha(int(event) / 10)
165+
sg.user_settings_set_entry('-alpha-', int(event) / 10)
166+
elif event == 'Set Refresh Rate':
167+
choice = sg.popup_get_text('How frequently to update window in seconds? (can be a float)', default_text=sg.user_settings_get_entry('-fresh frequency-', UPDATE_FREQUENCY_MILLISECONDS)/1000, location=window.current_location())
168+
if choice is not None:
169+
try:
170+
refresh_frequency = float(choice)*1000 # convert to milliseconds
171+
sg.user_settings_set_entry('-fresh frequency-', float(refresh_frequency))
172+
except Exception as e:
173+
sg.popup_error(f'You entered an incorrect number of seconds: {choice}', f'Error: {e}', location=window.current_location())
174+
elif event == 'New Theme':
175+
loc = window.current_location()
176+
if choose_theme(window.current_location(), window.size) is not None:
177+
window.close() # out with the old...
178+
window = make_window(loc) # in with the new
179+
180+
window.close()
181+
182+
183+
if __name__ == '__main__':
184+
# To start the window at a specific location, get this location on the command line
185+
# The location should be in form x,y with no spaces
186+
location = (None, None) # assume no location provided
187+
if len(sys.argv) > 1:
188+
location = sys.argv[1].split(',')
189+
location = (int(location[0]), int(location[1]))
190+
main(location)

0 commit comments

Comments
 (0)