renpy/launcher/game/choose_theme.rpy

516 lines
14 KiB
Plaintext
Raw Normal View History

2023-09-12 22:16:10 +00:00
# Copyright 2004-2023 Tom Rothamel <pytom@bishoujo.us>
2023-01-18 22:13:55 +00:00
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
init python:
import random
import codecs
import re
import sys
import future.utils
def theme_names():
"""
Gets a list of all of the theme names we know about.
"""
names = list(theme_data.THEME.keys())
names.sort(key=lambda a : a.lower())
return names
def scheme_names(theme):
"""
Gets a list of the color scheme names corresponding to the given
theme.
"""
names = list(theme_data.THEME[theme].keys())
names.sort(key=lambda a : a.lower())
return names
def theme_yinitial():
names = theme_names()
if len(names) < 2:
return 0
return 1.0 * names.index(current_theme) / (len(names) - 1)
def scheme_yinitial():
names = scheme_names(current_theme)
if len(names) < 2:
return 0
return 1.0 * names.index(current_scheme) / (len(names) - 1)
def pick_theme(theme, scheme):
"""
Returns a theme and scheme that are similar to `theme` and `scheme`.
If the theme is known, picks it, otherwise picks a random theme. If
the scheme is known for that theme, picks it, otherwise picks a
random scheme that is known for the current theme.
"""
if theme not in theme_data.THEME:
theme = random.choice(list(theme_data.THEME))
schemes = theme_data.THEME[theme]
if scheme not in schemes:
if theme in schemes:
scheme = theme
else:
scheme = random.choice(list(schemes))
return theme, scheme
def implement_theme(theme, scheme):
"""
Implement the current theme.
This function uses non-public APIs.
"""
global showing_theme, showing_scheme
if theme == showing_theme and scheme == showing_scheme:
return
renpy.style.restore(style_backup)
future.utils.exec_(theme_data.THEME[theme][scheme], globals(), globals())
# Rebuild the style cache.
renpy.style.rebuild(False)
# Bust the render cache, so we re-evaluate the styles.
renpy.display.interface.kill_textures()
showing_theme = theme
showing_scheme = scheme
renpy.restart_interaction()
showing_theme = None
showing_scheme = None
class SetTheme(Action):
def __init__(self, theme):
self.theme = theme
def __call__(self):
global current_theme
global current_scheme
current_theme, current_scheme = pick_theme(self.theme, current_scheme)
implement_theme(current_theme, current_scheme)
renpy.restart_interaction()
def get_selected(self):
return current_theme == self.theme
class SetScheme(Action):
def __init__(self, scheme):
self.scheme = scheme
def __call__(self):
global current_theme
global current_scheme
current_theme, current_scheme = pick_theme(current_theme, self.scheme)
implement_theme(current_theme, current_scheme)
renpy.restart_interaction()
def get_selected(self):
return current_scheme == self.scheme
class PreviewTheme(Action):
def __init__(self, theme, scheme):
self.theme = theme
self.scheme = scheme
def __call__(self):
theme, scheme = pick_theme(self.theme, self.scheme)
implement_theme(theme, scheme)
def unhovered(self):
if (showing_theme == self.theme and showing_scheme == self.scheme):
implement_theme(current_theme, current_scheme)
def value_changed(value):
return None
##########################################################################
# Code to update options.rpy
def list_logical_lines(filename):
"""
This reads in filename, and turns it into a list of logical
lines.
"""
f = codecs.open(filename, "rb", "utf-8")
data = f.read()
f.close()
# The result.
rv = [ ]
# The current position we're looking at in the buffer.
pos = 0
# Looping over the lines in the file.
while pos < len(data):
# The line that we're building up.
line = ""
# The number of open parenthesis there are right now.
parendepth = 0
# Looping over the characters in a single logical line.
while pos < len(data):
c = data[pos]
if c == '\n' and not parendepth:
rv.append(line)
pos += 1
# This helps out error checking.
line = ""
break
# Backslash/newline.
if c == "\\" and data[pos+1] == "\n":
pos += 2
line += "\\\n"
continue
# Parenthesis.
if c in ('(', '[', '{'):
parendepth += 1
if c in ('}', ']', ')') and parendepth:
parendepth -= 1
# Comments.
if c == '#':
while data[pos] != '\n':
line += data[pos]
pos += 1
continue
# Strings.
if c in ('"', "'", "`"):
delim = c
line += c
pos += 1
escape = False
while pos < len(data):
c = data[pos]
if escape:
escape = False
pos += 1
line += c
continue
if c == delim:
pos += 1
line += c
break
if c == '\\':
escape = True
line += c
pos += 1
continue
continue
line += c
pos += 1
if line:
rv.append(line)
return rv
def switch_theme():
"""
Switches the theme of the current project to the current theme
and color scheme. (As set in current_theme and current_scheme.)
"""
theme_code = theme_data.THEME[current_theme][current_scheme]
# Did we change the file at all?
changed = False
filename = os.path.join(project.current.path, "game/options.rpy")
with codecs.open(filename + ".new", "wb", "utf-8") as out:
for l in list_logical_lines(filename):
m = re.match(r' theme.(\w+)\(', l)
if (not changed) and m and (m.group(1) in theme_data.THEME_FUNCTIONS):
l = " " + theme_code
changed = True
out.write(l + "\n")
if changed:
try:
os.unlink(filename + ".bak")
except Exception:
pass
os.rename(filename, filename + ".bak")
os.rename(filename + ".new", filename)
else:
os.unlink(filename + ".new")
interface.error(_("Could not change the theme. Perhaps options.rpy was changed too much."))
# Now give the theme's screen-ops function a chance to make any
# necessary changes to the screens.rpy file
filename = os.path.join(project.current.path, "game/screens.rpy")
changed = False
try:
with codecs.open(filename + ".new", "wb", "utf-8") as out:
lines = list_logical_lines(filename)
lines = theme_data.THEME_SCREEN_OPERATIONS[current_theme](lines)
if lines != None:
for l in lines:
out.write(l + "\n")
changed = True
if changed:
try:
os.unlink(filename + ".bak")
except Exception:
pass
os.rename(filename, filename + ".bak")
os.rename(filename + ".new", filename)
except Exception as inst:
try:
# just in case
os.unlink(filename + ".new")
except Exception:
pass
pass
def make_style_backup():
"""
Call this to back up the styles. This should be called in a
translate python block in each translation.
"""
global style_backup
style_backup = renpy.style.backup()
config.change_language_callbacks.append(make_style_backup)
screen theme_demo:
window:
style "gm_root"
xpadding 5
ypadding 5
grid 1 1:
xfill True
style_group "prefs"
vbox:
frame:
style_group "pref"
has vbox
label _("Display")
textbutton _("Window") action SelectedIf(True)
textbutton _("Fullscreen") action ui.returns(None)
textbutton _("Planetarium") action None
frame:
style_group "pref"
has vbox
label _("Sound Volume")
bar style "slider" value .75 range 1.0 changed value_changed
textbutton "Test":
action ui.returns(None)
style "soundtest_button"
init -2 python:
style.pref_frame.xfill = True
style.pref_frame.xmargin = 5
style.pref_frame.top_margin = 5
style.pref_vbox.xfill = True
style.pref_button.size_group = "pref"
style.pref_button.xalign = 1.0
style.pref_slider.xmaximum = 192
style.pref_slider.xalign = 1.0
style.soundtest_button.xalign = 1.0
screen choose_theme:
default scheme_yadjustment = ui.adjustment()
default theme_yadjustment = ui.adjustment()
default first = True
python:
if first:
theme_yinitial_value = theme_yinitial()
scheme_yinitial_value = scheme_yinitial()
else:
theme_yinitial_value = None
scheme_yinitial_value = None
first = False
frame:
style_group "l"
style "l_root"
window:
has vbox
label _("Choose Theme")
hbox:
yfill True
# Theme selector.
frame:
style "l_indent"
bottom_margin HALF_SPACER_HEIGHT
xmaximum 225
has vbox
label _("Theme") style "l_label_small"
viewport:
scrollbars "vertical"
mousewheel True
yadjustment theme_yadjustment
yinitial theme_yinitial_value
has vbox
for i in theme_names():
textbutton "[i]":
action SetTheme(i)
hovered PreviewTheme(i, current_scheme)
style "l_list2"
# Color scheme selector.
frame:
style "l_indent"
bottom_margin HALF_SPACER_HEIGHT
xmaximum 225
has vbox
label _("Color Scheme") style "l_label_small"
viewport:
scrollbars "vertical"
mousewheel True
yadjustment scheme_yadjustment
yinitial scheme_yinitial_value
has vbox
for i in scheme_names(current_theme):
textbutton "[i]":
action SetScheme(i)
hovered PreviewTheme(current_theme, i)
style "l_list2"
# Preview
frame:
style "l_default"
background Frame(PATTERN, 0, 0, tile=True)
xpadding 5
ypadding 5
xfill True
yfill True
xmargin 20
bottom_margin 6
use theme_demo
textbutton _("Return") action Jump("front_page") style "l_left_button"
textbutton _("Continue") action Return(True) style "l_right_button"
label choose_theme_callable:
python:
current_theme, current_scheme = pick_theme(None, None)
implement_theme(current_theme, current_scheme)
call screen choose_theme
python hide:
with interface.error_handling(_("changing the theme")):
switch_theme()
return
label choose_theme:
call choose_theme_callable
jump front_page