Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5c49430
Add Fgui-Converter Entry
CursedOctopus Sep 22, 2025
c386335
Add fgui_converter modul.
CursedOctopus Sep 29, 2025
06fc751
Merge branch 'main' into OctopusBranch
CursedOctopus Oct 16, 2025
73b3cc5
Add independency in setup.cfg
CursedOctopus Oct 17, 2025
02a7c36
Modify README.md, add `-o` option to `pyside6-uic` command.
CursedOctopus Oct 17, 2025
0f867ce
Add null "input/output" popup and fix some Converter bug
CursedOctopus Oct 18, 2025
99cc204
Merge branch 'main' into OctopusBranch
CursedOctopus Oct 31, 2025
743a764
Restructured button converter method
CursedOctopus Nov 4, 2025
ac12c0e
Add text style within Button
CursedOctopus Nov 4, 2025
8076bc9
Update Text drop-shadow outline method
CursedOctopus Nov 5, 2025
f615030
Support "Slider" component
CursedOctopus Nov 7, 2025
a8d78a9
Support sliders in screen
CursedOctopus Nov 9, 2025
33634fc
Add special screens (choice, say) converting method.
CursedOctopus Nov 11, 2025
82b2e34
Some component, as button and slider, could get custom data from sour…
CursedOctopus Nov 11, 2025
616a52c
Add special screens (say, save, load) converting method.
CursedOctopus Nov 12, 2025
00f0ac6
Add special screens (history, gallery) converting method.
CursedOctopus Nov 13, 2025
96f9289
Children of screen could be shown or hide by Fgui controllers.
CursedOctopus Nov 13, 2025
1b3e15f
Fix issue of save/load screen lack of tag menu.
CursedOctopus Nov 14, 2025
dc54c12
Implementation hidden(part display) and scrollable with screen level.
CursedOctopus Dec 8, 2025
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,5 @@ src/preppipe_gui_pyside6/forms/generated
# version files for pyinstaller build
versionfile_*.txt

run_cli.py
run_gui.py
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ git config --local include.path $PWD/gitconfig

需要手动执行的有:(请在上述开发环境配置完毕后执行)
* 资源文件处理。请在获取[资源仓](#素材资源-assets)后,在仓库根目录下运行 `python3 ./build_assets.py` 以生成 `src/preppipe/assets/_install` 下的内容。该操作需要在资源列表更新时或任意资源类型保存的的内部数据结构改变时重新进行。
* GUI 中 PySide6 `.ui` 文件编译。请在 `src/preppipe_gui_pyside6/forms` 目录下将所有诸如 `xxx.ui` 的文件使用命令 `pyside6-uic xxx.ui generated/ui_xxx.py` 编译成 `.py`。如果您使用 Linux,您可以直接用该目录下的 `Makefile`。该操作需要在任意 .ui 文件更改后重新执行。
* GUI 中 PySide6 `.ui` 文件编译。请在 `src/preppipe_gui_pyside6/forms` 目录下将所有诸如 `xxx.ui` 的文件使用命令 `pyside6-uic xxx.ui -o generated/ui_xxx.py` 编译成 `.py`。如果您使用 Linux,您可以直接用该目录下的 `Makefile`。该操作需要在任意 .ui 文件更改后重新执行。

## GUI启动

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ install_requires =
xlsxwriter
psd-tools
antlr4-python3-runtime >= 4.10, < 4.11.0
lxml>=4.9.0

# GUI extra dependencies here
# https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#configuring-setup-using-setup-cfg-files
Expand Down
1,106 changes: 1,106 additions & 0 deletions src/fgui_converter/FguiAssetsParseLib.py

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/fgui_converter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-

# 将该目录视为可导入的 Python 包。

2,237 changes: 2,237 additions & 0 deletions src/fgui_converter/utils/renpy/Fgui2RenpyConverter.py

Large diffs are not rendered by default.

175 changes: 175 additions & 0 deletions src/fgui_converter/utils/renpy/renpy_templates/01_renpy_cdd.rpy
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
python early:

import pygame
import math

class ButtonContainer(renpy.display.behavior.Button):
"""
按钮容器类,按下后有缩放和改变颜色(未实现)效果。
"""
def __init__(self, pressed_scale=1.0, pressed_dark=1.0, *args, **kwargs):
super(ButtonContainer, self).__init__(**kwargs)
self.pressed_scale = pressed_scale
# FGUI中变暗的取值范围为0~1,0完全黑,1完全无效果。(编辑器中允许输入值超过1,但无效果。)
# 此处使用BrightnessMatrix类,入参取值范围-1~1,-1完全变黑,0完全无效果,1完全变白。
# 因此需要做一个转换
self.pressed_dark = min(pressed_dark, 1.0) - 1.0
self.brightness_matrix = BrightnessMatrix(value=self.pressed_dark)
self.button_pressed = False
self.width = 0
self.height = 0
self.blit_pos = (0, 0)

def render(self, width, height, st, at):
if self.button_pressed and self.pressed_dark != 0:
t = Transform(child=self.child, anchor=(0.5, 0.5), matrixcolor=self.brightness_matrix)
else:
t = Transform(child=self.child, anchor=(0.5, 0.5), matrixcolor=None)
child_render = renpy.render(t, width, height, st, at)
self.width, self.height = child_render.get_size()
self.size = (self.width, self.height)
render = renpy.Render(self.width, self.height)
if self.button_pressed:
if self.pressed_scale != 1.0:
child_render.zoom(self.pressed_scale, self.pressed_scale)
# 为了居中,重新计算blit坐标
self.blit_pos = ((int)(self.width*(1-self.pressed_scale)/2), (int)(self.height*(1-self.pressed_scale)/2))
else:
self.blit_pos = (0, 0)
render.blit(child_render, self.blit_pos)
return render

def event(self, ev, x, y, st):
if renpy.map_event(ev, "mousedown_1") and renpy.is_pixel_opaque(self.child, self.width, self.height, st=st, at=0, x=x, y=y) and not self.button_pressed:
self.button_pressed = True
renpy.redraw(self, 0)
return self.child.event(ev, x, y, st)
if self.button_pressed:
if renpy.map_event(ev, "mouseup_1"):
self.button_pressed = False
renpy.redraw(self, 0)
elif ev.type == pygame.MOUSEMOTION and ev.buttons[0] != 1 :
self.button_pressed = False
renpy.redraw(self, 0)
return self.child.event(ev, x, y, st)

def visit(self):
return [ self.child ]

python early:
renpy.register_sl_displayable("button_container", ButtonContainer, "pressed_button", 1)\
.add_property("pressed_scale")\
.add_property("pressed_dark")\
.add_property_group("button")

init python:
class SquenceAnimator(renpy.Displayable):
"""
多图序列帧动画组件。
"""
def __init__(self, prefix, separator, begin_index, end_index, interval, loop=True, **kwargs):
super(SquenceAnimator, self).__init__(**kwargs)
self.prefix = prefix
self.separator = separator
self.begin_index = begin_index
self.end_index = end_index
self.length = end_index - begin_index + 1


self.sequence = []
for i in range(begin_index, end_index+1):
self.sequence.append(renpy.displayable(self.prefix + self.separator + str(i)))

self.current_index = 0
self.show_timebase = 0

self.interval = interval
self.loop = loop

def render(self, width, height, st, at):
## st为0时,表示组件重新显示
if st == 0:
self.show_timebase = 0
self.current_index = 0
if (st >= (self.show_timebase + self.interval)):
self.show_timebase = st
self.current_index += 1
if self.current_index >= self.length:
if self.loop:
self.current_index = 0
else:
self.current_index = self.length - 1

render = renpy.render(self.sequence[self.current_index], width, height, st, at)
renpy.redraw(self, 0)

return render

# 重置序列帧
def reset_sequence_index(self):
self.current_index = 0

def get_frame_image(self, index):
return self.sequence[index]

class SquenceAnimator2(renpy.Displayable):
"""
单图序列帧动画组件。
"""
def __init__(self, img, row, column, interval, loop=True, **kwargs):

super(SquenceAnimator2, self).__init__(**kwargs)
# im入参是字符串,需要转为Image对象,获取尺寸信息
self.img = Image(img)
self.size = renpy.image_size(self.img)
# 行数
self.row = row
# 列数
self.column = column
# 单帧宽度
self.frame_width = int(self.size[0] / column)
# 单帧高度
self.frame_height = int(self.size[1] / row)
# 序列帧长度
self.length = row * column

self.sequence = []
# 循环嵌套切割单帧图像
for i in range(row):
for j in range(column):
# im.Crop()已被标记为deprecated,但剪裁边缘正确。
# Crop()方法在右、低两边会有错误。
# 参考 https://github.com/renpy/renpy/issues/6376
self.sequence.append(im.Crop(self.img, (self.frame_width*j, self.frame_height*i, self.frame_width, self.frame_height)))

self.current_index = 0
self.show_timebase = 0

self.interval = interval
self.loop = loop

def render(self, width, height, st, at):
## st为0时,表示组件重新显示
if st == 0:
self.show_timebase = 0
self.current_index = 0
if (st >= (self.show_timebase + self.interval)):
self.show_timebase = st
self.current_index += 1
if self.current_index >= self.length:
if self.loop:
self.current_index = 0
else:
self.current_index = self.length - 1

render = renpy.render(self.sequence[self.current_index], width, height, st, at)
renpy.redraw(self, 0)

return render

# 重置序列帧
def reset_sequence_index(self):
self.current_index = 0

def get_frame_image(self, index):
return self.sequence[index]
99 changes: 99 additions & 0 deletions src/fgui_converter/utils/renpy/renpy_templates/02_renpy_shader.rpy
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
init python:

renpy.register_shader("CursedOctopus.rectangle", variables="""
uniform vec4 u_rectangle_color;
uniform vec4 u_stroke_color;
uniform vec2 u_model_size;
uniform float u_radius;
uniform float u_thickness;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
""", vertex_300="""
v_tex_coord = a_tex_coord;
""",fragment_functions="""
float roundedBoxSDF(vec2 pos, vec2 border, float radius){
vec2 dis = abs(pos) - border + vec2(radius,radius);
return length(max(dis, 0.0)) + min(max(dis.x, dis.y), 0.0) - radius;
}
""",fragment_300="""
vec2 uv = v_tex_coord - vec2(0.5, 0.5);
vec2 tex_pos = uv * u_model_size;
float out_distance = roundedBoxSDF(tex_pos, u_model_size/2, u_radius);
float border_alpha = (1.0 - step(0.0, out_distance));
float in_distance = roundedBoxSDF(tex_pos, u_model_size/2-vec2(u_thickness,u_thickness), u_radius);
float fill_alpha = (1.0 - step(0.0, in_distance));
vec4 c1 = step(1-fill_alpha, 0) * u_rectangle_color;
vec4 c2 = step(fill_alpha, 0) * border_alpha * u_stroke_color;
gl_FragColor = c1 + c2;
""")

renpy.register_shader("CursedOctopus.rectangleAA", variables="""
uniform vec4 u_rectangle_color;
uniform vec4 u_stroke_color;
uniform vec2 u_model_size;
uniform float u_radius;
uniform float u_thickness;
uniform float u_edge_softness;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
""", vertex_300="""
v_tex_coord = a_tex_coord;
""",fragment_functions="""
float roundedBoxSDF(vec2 pos, vec2 border, float radius){
vec2 dis = abs(pos) - border + vec2(radius,radius);
return length(max(dis, 0.0)) + min(max(dis.x, dis.y), 0.0) - radius;
}
""",fragment_300="""
vec2 uv = v_tex_coord - vec2(0.5, 0.5);
vec2 tex_pos = uv * u_model_size;
float out_distance = roundedBoxSDF(tex_pos, u_model_size/2, u_radius);
float border_alpha = (1.0 - smoothstep(-u_edge_softness, u_edge_softness, out_distance));
float in_distance = roundedBoxSDF(tex_pos, u_model_size/2-vec2(u_thickness,u_thickness), u_radius);
float fill_alpha = (1.0 - smoothstep(0, u_edge_softness, in_distance));
vec4 c1 = fill_alpha * u_rectangle_color;
vec4 c2 = border_alpha * u_stroke_color;
gl_FragColor = mix(c2, c1, fill_alpha);
""")

renpy.register_shader("CursedOctopus.ellipse", variables="""
uniform vec4 u_ellipse_color;
uniform vec4 u_stroke_color;
uniform vec2 u_model_size;
uniform float u_thickness;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
""", vertex_300="""
v_tex_coord = a_tex_coord;
""",fragment_300="""
vec2 uv = v_tex_coord - vec2(0.5, 0.5);
float out_distance = length(uv);
float border_alpha = step(out_distance, 0.5);
vec2 tex_pos = uv * u_model_size;
float in_distance = length((abs(tex_pos+normalize(uv*u_thickness)*u_thickness))/u_model_size);
float fill_alpha = step(in_distance, 0.5);
vec4 c1 = step(1-fill_alpha, 0) * u_ellipse_color;
vec4 c2 = step(fill_alpha, 0) * border_alpha * u_stroke_color;
gl_FragColor = c1 + c2;
""")

renpy.register_shader("CursedOctopus.ellipseAA", variables="""
uniform vec4 u_ellipse_color;
uniform vec4 u_stroke_color;
uniform vec2 u_model_size;
uniform float u_thickness;
uniform float u_edge_softness;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
""", vertex_300="""
v_tex_coord = a_tex_coord;
""",fragment_300="""
vec2 uv = v_tex_coord - vec2(0.5, 0.5);
float out_distance = length(uv);
float border_alpha = smoothstep(out_distance-u_edge_softness, out_distance, 0.5-u_edge_softness);
vec2 tex_pos = uv * u_model_size;
float in_distance = length((abs(tex_pos+normalize(uv*u_thickness)*u_thickness))/u_model_size);
float fill_alpha = smoothstep(in_distance-u_edge_softness, in_distance, 0.5-u_edge_softness);
vec4 c1 = fill_alpha * u_ellipse_color;
vec4 c2 = border_alpha * u_stroke_color;
gl_FragColor = mix(c2, c1, fill_alpha);
""")
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 抗锯齿的椭圆(圆形)图形定义
image {image_name}:
Model().child(Solid('000')).shader('CursedOctopus.ellipseAA').uniform('u_ellipse_color', {ellipse_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_thickness', {stroke_thickness}).uniform('u_edge_softness', 0.01)
xysize {xysize}
pos {pos}
anchor {anchor}
xoffset {xoffset}
yoffset {yoffset}
transform_anchor {transform_anchor}
rotate {rotate}
alpha {alpha}
xzoom {xzoom}
yzoom {yzoom}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 椭圆(圆形)图形定义
image {image_name}:
Model().child(Solid('000')).shader('CursedOctopus.ellipse').uniform('u_ellipse_color', {ellipse_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_thickness', {stroke_thickness})
xysize {xysize}
pos {pos}
anchor {anchor}
xoffset {xoffset}
yoffset {yoffset}
transform_anchor {transform_anchor}
rotate {rotate}
alpha {alpha}
xzoom {xzoom}
yzoom {yzoom}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
init python:
import os
font_name_list = [{font_name_list}]
font_file_ext = [".ttf", ".ttc", ".otf"]

for font_name in font_name_list:
for font_ext in font_file_ext:
font_file_name = f"{font_name}{font_ext}"
if os.path.exists(f"{config.gamedir}\\{font_file_name}") or os.path.exists(f"{config.gamedir}\\fonts\\{font_file_name}"):
config.font_name_map[font_name] = font_file_name
renpy.log(config.font_name_map)
break
else:
print(f"Could not Find font: {font_name}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
image {image_name}:
Null(width={width}, height={height})
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 带光滑渐变的圆角矩形图形定义
image {image_name}:
Model().child(Solid('000')).shader('CursedOctopus.rectangleAA').uniform('u_rectangle_color', {rectangle_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_radius', {round_radius}).uniform('u_thickness', {stroke_thickness}).uniform('u_edge_softness', 1.0)
xysize {xysize}
pos {pos}
anchor {anchor}
xoffset {xoffset}
yoffset {yoffset}
transform_anchor {transform_anchor}
rotate {rotate}
alpha {alpha}
xzoom {xzoom}
yzoom {yzoom}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 圆角矩形图形定义
image {image_name}:
Model().child(Solid('000')).shader('CursedOctopus.rectangle').uniform('u_rectangle_color', {rectangle_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_radius', {round_radius}).uniform('u_thickness', {stroke_thickness})
xysize {xysize}
pos {pos}
anchor {anchor}
xoffset {xoffset}
yoffset {yoffset}
transform_anchor {transform_anchor}
rotate {rotate}
alpha {alpha}
xzoom {xzoom}
yzoom {yzoom}
Loading