|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 ZYKsslm 于 2024-10-5 18:03 编辑
Positioner:一个Ren'Py定位工具
Positioner 是一个在 Ren'Py 中快速使用矩形定位的工具
通过不同的定位工具可以快速确定位置参数(目前只有绝对坐标),同时支持实时拖动调整和滚轮缩放
源码:
核心部分:
[RenPy] 纯文本查看 复制代码 #* Positioner - 一个开源的 Ren'Py 定位工具
#* 作者 ZYKsslm
#! 开源协议 MIT
#* 感谢 Feniks [url=home.php?mod=space&uid=1671]@[/url] feniksdev.com 提供的非常实用的开源工具 color_picker
python early:
import pygame
class Positioner(renpy.Displayable):
def __init__(self, name="", size=(100, 100), color=Color("#00d9ff", alpha=0.7), **properties):
super().__init__(**properties)
self._name = name
self._name_color = Color("#e5ff00")
self.name_displayable = Text(str(name), color=self.name_color)
self._size = size # 大小
self._color = color
self._pos = (0, 0) # 左上角顶点的坐标
self._relative_size = (0, 0)
self.rect = (*self.pos, *self.size)
self.pressed = False
self.lock = False
self.follow_mouse = False
self.show = True
@property
def pos(self):
return self._pos
@pos.setter
def pos(self, value):
self._pos = value
self.rect = (*self.pos, *self.size)
self._update_display()
@property
def size(self):
return self._size
@size.setter
def size(self, value):
self._size = value
self.rect = (*self.pos, *self.size)
self._update_display()
@property
def color(self):
return self._color
@color.setter
def color(self, value):
self._color = Color(color=value.hexcode, alpha=0.7)
self._update_display()
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
self.name_displayable = Text(str(value), color=self.name_color)
self._update_display()
@property
def name_color(self):
return self._name_color
@name_color.setter
def name_color(self, value):
self._name_color = value
self.name_displayable = Text(str(self.name), color=self.name_color)
self._update_display()
def _update_display(self):
renpy.redraw(self, 0)
renpy.restart_interaction()
def toggle_lock(self):
self.lock = not self.lock
self._update_display()
def toggle_show(self):
self.show = not self.show
self._update_display()
def toggle_follow_mouse(self):
self.follow_mouse = not self.follow_mouse
self._update_display()
def reset(self):
self.size = (100, 100)
self.pos = (0, 0)
self._relative_size = (0, 0)
self._update_display()
def modify_size(self, factor, x=True, y=True):
if x:
self.size = (round(self.size[0] * factor), self.size[1])
if y:
self.size = (self.size[0], round(self.size[1] * factor))
self._update_display()
def plus(self, x=True, y=True):
self.modify_size(1.1, x, y)
def minus(self, x=True, y=True):
self.modify_size(0.9, x, y)
def render(self, width, height, st, at):
render = renpy.Render(width, height)
if self.show:
canvas = render.canvas()
canvas.rect(self.color, (*self.pos, *self.size))
if self.name:
name_render = renpy.render(self.name_displayable, width, height, st, at)
render.blit(name_render, self.pos)
return render
def event(self, ev, x, y, st):
if self.lock:
return
if ev.type == pygame.MOUSEBUTTONDOWN:
if ev.button == 1:
if self.rect[0] <= x <= self.rect[0] + self.rect[2] and self.rect[1] <= y <= self.rect[1] + self.rect[3]:
self._relative_size = (x - self.pos[0], y - self.pos[1])
self.pressed = True
elif ev.button == 4:
self.plus()
elif ev.button == 5:
self.minus()
elif ev.type == pygame.MOUSEBUTTONUP:
self.pressed = False
self._relative_size = (0, 0)
if self.pressed or self.follow_mouse:
if self.pressed:
self.follow_mouse = False
self.pos = (x - self._relative_size[0], y - self._relative_size[1])
renpy.restart_interaction()
renpy.redraw(self, 0)
class PositionerGroup(renpy.Displayable):
def __init__(self, *positioners, **properties):
super().__init__(**properties)
self.positioners = list(positioners)
if not self.positioners:
self.create()
else:
self.selected_positioner = self.positioners[-1]
def create(self, *args, **kwargs):
positioner = Positioner(*args, **kwargs)
self.positioners.append(positioner)
self.selected_positioner = positioner
renpy.redraw(self, 0)
def remove(self, positioner):
self.positioners.remove(positioner)
if not self.positioners:
self.create()
self.selected_positioner = self.positioners[-1]
renpy.redraw(self, 0)
def render(self, width, height, st, at):
render = renpy.Render(width, height)
for positioner in self.positioners:
render.blit(positioner.render(width, height, st, at), (0, 0))
return render
def event(self, ev, x, y, st):
if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
for positioner in self.positioners:
area = (*positioner.pos, *positioner.size)
if area[0] <= x <= area[0] + area[2] and area[1] <= y <= area[1] + area[3]:
self.selected_positioner = positioner
renpy.restart_interaction()
self.selected_positioner.event(ev, x, y, st)
renpy.redraw(self, 0)
def visit(self):
return self.positioners
screen color_picker(obj, field, default_color):
modal True
style_prefix 'cpicker'
default picker = ColorPicker(500, 500, default_color)
default picker_swatch = DynamicDisplayable(picker_color, picker=picker, xsize=100, ysize=100)
default picker_hex = DynamicDisplayable(picker_hexcode, picker=picker)
label "{i}color_picker{/i} 工具由 {u}@ feniksdev.com{/u} 提供"
hbox:
vbar value FieldValue(picker, "hue_rotation", 1.0)
vbox:
add picker
bar value FieldValue(picker, "hue_rotation", 1.0)
vbox:
xsize 200 spacing 10 align (0.0, 0.0)
add picker_swatch
add picker_hex
textbutton "完成" action [SetField(obj, field, picker.color), Return()]
style cpicker_vbox:
align (0.5, 0.5)
spacing 25
style cpicker_hbox:
align (0.5, 0.5)
spacing 25
style cpicker_vbar:
xysize (50, 500)
base_bar At(Transform("#000", xysize=(50, 500)), spectrum(horizontal=False))
thumb Transform("selector_bg", xysize=(50, 20))
thumb_offset 10
style cpicker_bar:
xysize (500, 50)
base_bar At(Transform("#000", xysize=(500, 50)), spectrum())
thumb Transform("selector_bg", xysize=(20, 50))
thumb_offset 10
style cpicker_text:
color "#fff"
style cpicker_button:
padding (4, 4) insensitive_background "#fff"
style cpicker_button_text:
color "#aaa"
hover_color "#fff"
style cpicker_image_button:
xysize (104, 104)
padding (4, 4)
hover_foreground "#fff2"
screen change_positioner_name(positioner):
modal True
default notice_value = FieldInputValue(positioner, "name")
frame:
xysize (500, 300)
align (0.5, 0.5)
label "请输入名称:" align (0.5, 0.15)
input:
align (0.5, 0.5)
pixel_width 390
multiline True
copypaste True
value notice_value
hbox:
spacing 100
align (0.5, 0.75)
textbutton "颜色" action ShowMenu("color_picker", obj=positioner, field="name_color", default_color=positioner.name_color)
textbutton "完成" action Hide("change_positioner_name")
screen position_helper(*displayables):
default positioner_group = PositionerGroup()
$ positioner = positioner_group.selected_positioner
for displayable in displayables:
add displayable
add positioner_group
frame:
background Color("#ffffff", alpha=0.3)
align (0.02, 0.1)
has vbox
spacing 20
label "当前参数"
label "[positioner.rect]"
label "x&y轴"
textbutton "放大" action Function(positioner.plus)
textbutton "缩小" action Function(positioner.minus)
textbutton "重置" action Function(positioner.reset)
label "x轴"
textbutton "放大" action Function(positioner.plus, y=False)
textbutton "缩小" action Function(positioner.minus, y=False)
label "y轴"
textbutton "放大" action Function(positioner.plus, x=False)
textbutton "缩小" action Function(positioner.minus, x=False)
frame:
background Color("#ffffff", alpha=0.3)
align (0.98, 0.1)
has vbox
spacing 20
label "状态"
hbox:
spacing 5
text "名称:"
add positioner.name_displayable
text "位置: [positioner.pos]"
text "大小: [positioner.size]"
text "锁定: [positioner.lock]"
text "显示: [positioner.show]"
text "跟随: [positioner.follow_mouse]"
label "操作"
textbutton "锁定/解锁" action Function(positioner.toggle_lock)
textbutton "显示/隐藏" action Function(positioner.toggle_show)
textbutton "跟随/取消" action Function(positioner.toggle_follow_mouse)
textbutton "修改定位工具名称" action Show("change_positioner_name", positioner=positioner)
textbutton "修改定位工具颜色" action ShowMenu("color_picker", obj=positioner, field="color", default_color=positioner.color)
textbutton "创建定位工具" action Function(positioner_group.create)
textbutton "删除定位工具" action Function(positioner_group.remove, positioner)
color_picker 工具部分(由 Feniks @ feniksdev.com 提供)
[RenPy] 纯文本查看 复制代码 ################################################################################
##
## Color Picker for Ren'Py by Feniks (feniksdev.itch.io / feniksdev.com)
##
################################################################################
## This file contains code for a colour picker in Ren'Py.
## If you use this code in your projects, credit me as Feniks @ feniksdev.com
##
## If you'd like to see how to use this tool, check the other file,
## color_picker_examples.rpy!
## You can also see this tool in action in the image tint tool, also on itch:
## [url=https://feniksdev.itch.io/image-tint-tool]https://feniksdev.itch.io/image-tint-tool[/url]
##
## Leave a comment on the tool page on itch.io or an issue on the GitHub
## if you run into any issues.
## [url=https://feniksdev.itch.io/color-picker-for-renpy]https://feniksdev.itch.io/color-picker-for-renpy[/url]
## [url=https://github.com/shawna-p/renpy-color-picker]https://github.com/shawna-p/renpy-color-picker[/url]
################################################################################
################################################################################
## SHADERS & TRANSFORMS
################################################################################
init python:
## A shader which creates a gradient for a colour picker.
renpy.register_shader("feniks.color_picker", variables="""
uniform vec4 u_gradient_top_right;
uniform vec4 u_gradient_top_left;
uniform vec4 u_gradient_bottom_left;
uniform vec4 u_gradient_bottom_right;
uniform vec2 u_model_size;
varying float v_gradient_x_done;
varying float v_gradient_y_done;
attribute vec4 a_position;
""", vertex_300="""
v_gradient_x_done = a_position.x / u_model_size.x;
v_gradient_y_done = a_position.y / u_model_size.y;
""", fragment_300="""
// Mix the two top colours
vec4 top = mix(u_gradient_top_left, u_gradient_top_right, v_gradient_x_done);
// Mix the two bottom colours
vec4 bottom = mix(u_gradient_bottom_left, u_gradient_bottom_right, v_gradient_x_done);
// Mix the top and bottom
gl_FragColor = mix(bottom, top, 1.0-v_gradient_y_done);
""")
## A shader which creates a spectrum. Generally for colour pickers.
renpy.register_shader("feniks.spectrum", variables="""
uniform float u_lightness;
uniform float u_saturation;
uniform float u_horizontal;
uniform vec2 u_model_size;
varying float v_gradient_x_done;
varying float v_gradient_y_done;
attribute vec4 a_position;
""", vertex_300="""
v_gradient_x_done = a_position.x / u_model_size.x;
v_gradient_y_done = a_position.y / u_model_size.y;
""", fragment_functions="""
// HSL to RGB conversion adapted from
// [url=https://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion]https://stackoverflow.com/questi ... gb-color-conversion[/url]
float hue2rgb(float p, float q, float t){
if(t < 0.0) t += 1.0;
if(t > 1.0) t -= 1.0;
if(t < 1.0/6.0) return p + (q - p) * 6.0 * t;
if(t < 1.0/2.0) return q;
if(t < 2.0/3.0) return p + (q - p) * (2.0/3.0 - t) * 6.0;
return p;
}
vec3 hslToRgb(float h, float l, float s) {
float q = l < 0.5 ? l * (1.0 + s) : l + s - l * s;
float p = 2.0 * l - q;
float r = hue2rgb(p, q, h + 1.0/3.0);
float g = hue2rgb(p, q, h);
float b = hue2rgb(p, q, h - 1.0/3.0);
return vec3(r, g, b);
}
""", fragment_300="""
float hue = u_horizontal > 0.5 ? v_gradient_x_done : 1.0-v_gradient_y_done;
vec3 rgb = hslToRgb(hue, u_lightness, u_saturation);
gl_FragColor = vec4(rgb.r, rgb.g, rgb.b, 1.0);
""")
## A transform which creates a spectrum.
## If horizontal is True, the spectrum goes from left to right instead of
## top to bottom. You can also adjust the lightness and saturation
## (between 0 and 1).
transform spectrum(horizontal=True, light=0.5, sat=1.0):
shader "feniks.spectrum"
u_lightness light
u_saturation sat
u_horizontal float(horizontal)
## A transform which creates a square with a gradient. By default, only the
## top right colour is required (to make a colour picker gradient) but four
## corner colours may also be provided clockwise from the top-right.
transform color_picker(top_right, bottom_right="#000", bottom_left="#000",
top_left="#fff"):
shader "feniks.color_picker"
u_gradient_top_right Color(top_right).rgba
u_gradient_top_left Color(top_left).rgba
u_gradient_bottom_left Color(bottom_left).rgba
u_gradient_bottom_right Color(bottom_right).rgba
################################################################################
## CLASSES AND FUNCTIONS
################################################################################
init python:
import pygame
class ColorPicker(renpy.Displayable):
"""
A CDD which allows the player to pick a colour between four
corner colours, with the typical setup used for a colour picker.
Attributes
----------
xsize : int
The width of the colour picker.
ysize : int
The height of the colour picker.
top_left : Color
The colour of the top-left corner.
top_right : Color
The colour of the top-right corner.
bottom_left : Color
The colour of the bottom-left corner.
bottom_right : Color
The colour of the bottom-right corner.
color : Color
The current colour the colour picker is focused over.
selector_xpos : float
The xpos of the colour selector.
selector_ypos : float
The ypos of the colour selector.
picker : Displayable
A square that is used to display the colour picker.
hue_rotation : float
The amount the current hue is rotated by.
dragging : bool
True if the indicator is currently being dragged around.
saved_colors : dict
A dictionary of key - Color pairs corresponding to colours the
picker has selected in the past.
last_saved_color : any
The dictionary key of the last colour saved.
mouseup_callback : callable
An optional callback or list of callbacks which will be called when
the player lifts their mouse after selecting a colour.
"""
RED = Color("#f00")
def __init__(self, xsize, ysize, start_color=None, four_corners=None,
saved_colors=None, last_saved_color=None, mouseup_callback=None,
**kwargs):
"""
Create a ColorPicker object.
Parameters:
-----------
xsize : int
The width of the colour picker.
ysize : int
The height of the colour picker.
start_color : str
A hexadecimal colour code corresponding to the starting colour.
four_corners : tuple(Color, Color, Color, Color)
A tuple of four colours corresponding to the four corners of the
colour picker. The order is top right, bottom right, bottom
left, top left. If this is not None, it will override the
start_color parameter.
saved_colors : dict
A dictionary of key - Color pairs corresponding to colours
the picker has selected in the past.
last_saved_color : any
The dictionary key of the last colour saved.
mouseup_callback : callable
An optional callback or list of callbacks which will be called
when the player lifts their mouse after selecting a colour.
"""
super(ColorPicker, self).__init__(**kwargs)
self.xsize = xsize
self.ysize = ysize
self.top_left = None
self.top_right = None
self.bottom_left = None
self.bottom_right = None
self.last_saved_color = last_saved_color
self.saved_colors = saved_colors or dict()
self.mouseup_callback = mouseup_callback
if start_color is None and four_corners is None:
## Automatically start with red
self.set_color("#f00")
elif four_corners is None:
self.set_color(start_color)
else:
all_corners = [Color(c) if not isinstance(c, Color) else c for c in four_corners]
self.top_right, self.bottom_right, self.bottom_left, self.top_left = all_corners
self.set_color(self.top_right)
self.picker = Transform("#fff", xysize=(self.xsize, self.ysize))
self.dragging = False
self.save_color(self.last_saved_color)
def set_color(self, color):
"""
Set the current colour of the colour picker.
Parameters
----------
color : Color
The new colour to set the colour picker to.
"""
if not isinstance(color, Color):
self.color = Color(color)
else:
self.color = color
self.dragging = False
## Check if this has four custom corners
if self.top_left is None:
## No; set to saturation/value
self.selector_xpos = round(self.color.hsv[1]*255.0)/255.0
self.selector_ypos = 1.0 - round(self.color.hsv[2]*255.0)/255.0
self._hue_rotation = self.color.hsv[0]
else:
## There isn't a good way to guess the position of a colour
## with custom corners, so just set it to the top right
self.selector_xpos = 1.0
self.selector_ypos = 0.0
self._hue_rotation = 0.0
@property
def hue_rotation(self):
"""
The hue rotation of the colour picker.
"""
return self._hue_rotation
@hue_rotation.setter
def hue_rotation(self, value):
"""
Set the hue rotation of the colour picker.
"""
if value > 1.0:
value = value % 1.0
if round(self._hue_rotation*255.0) == round(value*255):
return
self._hue_rotation = value
self.update_hue()
def set_saved_color(self, key, new_color):
"""
Set the colour saved with key as the key to new_color.
Parameters
----------
key : any
The key of the colour to change. Must be a valid dictionary key.
new_color : Color
The new colour to set the saved colour to.
"""
if not isinstance(new_color, Color):
self.saved_colors[key] = Color(new_color)
else:
self.saved_colors[key] = new_color
def save_color(self, key):
"""
Save the current colour to the saved dictionary with key as the key.
"""
self.saved_colors[key] = self.color
def get_color(self, key):
"""
Retrieve the colour saved in the dictionary with key as the key.
"""
return self.saved_colors.get(key, Color("#000"))
def swap_to_saved_color(self, key):
"""
Swap to the saved colour with key as the key.
"""
self.set_color(self.saved_colors.get(key, Color("#000")))
self.last_saved_color = key
renpy.redraw(self, 0)
def render(self, width, height, st, at):
"""
Render the displayable to the screen.
"""
r = renpy.Render(self.xsize, self.ysize)
if self.top_left is None:
trc = self.RED.rotate_hue(self.hue_rotation)
# Colorize the picker into a gradient
picker = At(self.picker, color_picker(trc))
else:
# Custom four corners; no spectrum sliders
picker = At(self.picker, color_picker(
self.top_right.rotate_hue(self.hue_rotation),
self.bottom_right.rotate_hue(self.hue_rotation),
self.bottom_left.rotate_hue(self.hue_rotation),
self.top_left.rotate_hue(self.hue_rotation)))
# Position the selector
selector = Transform("selector", anchor=(0.5, 0.5),
xpos=self.selector_xpos, ypos=self.selector_ypos)
final = Fixed(picker, selector, xysize=(self.xsize, self.ysize))
# Render it to the screen
ren = renpy.render(final, self.xsize, self.ysize, st, at)
r.blit(ren, (0, 0))
return r
def update_hue(self):
"""
Update the colour based on the hue in the top-right corner
(or in all 4 corners).
"""
# Figure out the colour under the selector
if self.top_left is None:
trc = self.RED.rotate_hue(self.hue_rotation)
tlc = Color("#fff")
brc = Color("#000")
blc = Color("#000")
else:
tlc = self.top_left.rotate_hue(self.hue_rotation)
trc = self.top_right.rotate_hue(self.hue_rotation)
brc = self.bottom_right.rotate_hue(self.hue_rotation)
blc = self.bottom_left.rotate_hue(self.hue_rotation)
self.color = tlc.interpolate(trc, self.selector_xpos)
bottom = blc.interpolate(brc, self.selector_xpos)
self.color = self.color.interpolate(bottom, self.selector_ypos)
self.save_color(self.last_saved_color)
renpy.redraw(self, 0)
def event(self, ev, x, y, st):
"""Allow the user to drag their mouse to select a colour."""
relative_x = round(x/float(self.xsize)*255.0)/255.0
relative_y = round(y/float(self.ysize)*255.0)/255.0
in_range = (0.0 <= relative_x <= 1.0) and (0.0 <= relative_y <= 1.0)
if renpy.map_event(ev, "mousedown_1") and in_range:
self.dragging = True
self.selector_xpos = relative_x
self.selector_ypos = relative_y
elif ev.type == pygame.MOUSEMOTION and self.dragging:
self.selector_xpos = relative_x
self.selector_ypos = relative_y
elif renpy.map_event(ev, "mouseup_1") and self.dragging:
self.dragging = False
## Update the screen
renpy.restart_interaction()
if self.mouseup_callback is not None:
renpy.run(self.mouseup_callback, self)
return
else:
return
# Limit x/ypos
self.selector_xpos = min(max(self.selector_xpos, 0.0), 1.0)
self.selector_ypos = min(max(self.selector_ypos, 0.0), 1.0)
self.update_hue()
return None
def picker_color(st, at, picker, xsize=100, ysize=100):
"""
A DynamicDisplayable function to update the colour picker swatch.
Parameters:
-----------
picker : ColorPicker
The picker this swatch is made from.
xsize : int
The width of the swatch.
ysize : int
The height of the swatch.
"""
return Transform(picker.color, xysize=(xsize, ysize)), 0.01
def picker_hexcode(st, at, picker):
"""
A brief DynamicDisplayable demonstration of how to display color
information in real-time.
"""
return Text(picker.color.hexcode, style='picker_hexcode'), 0.01
################################################################################
## IMAGES
################################################################################
init offset = -1
init python:
def construct_selector(w=2, sz=5):
"""
Constructs a white box surrounded by a black box, to use as a
selector for the colour picker.
Parameters
----------
w : int
The width of the lines.
sz : int
The size of the inner box.
"""
## First, the sides of the box
box_leftright = [
Transform("#000", xysize=(w, sz+2*3*w), align=(0.5, 0.5)),
Transform("#fff", xysize=(w, sz+2*2*w), align=(0.5, 0.5)),
Transform("#000", xysize=(w, sz+2*1*w), align=(0.5, 0.5)),
]
## Then the top and bottom
box_topbottom = [
Transform("#000", xysize=(sz+2*2*w, w), align=(0.5, 0.5)),
Transform("#fff", xysize=(sz+2*1*w, w), align=(0.5, 0.5)),
Transform("#000", xysize=(sz, w), align=(0.5, 0.5)),
]
final_vbox = box_topbottom + [Null(height=sz)] + box_topbottom[::-1]
final_hbox = (box_leftright + [Null(width=-w*2)]
+ [VBox(*final_vbox, style='empty', spacing=0)]
+ [Null(width=-w*2)] + box_leftright[::-1])
## Now put it together
return HBox(*final_hbox, spacing=0, style='empty')
## These can be changed; see color_picker_examples.rpy for more.
## Feel free to remove the constructor function above if you don't use these.
## Used for both the spectrum thumb and the colour indicator.
image selector_img = construct_selector(2, 3)
image selector_bg = Frame("selector_img", 7, 7)
## The image used for the indicator showing the current colour.
image selector = Transform("selector_bg", xysize=(15, 15))
style picker_hexcode:
color "#fff"
font "DejaVuSans.ttf"
使用方式:
- 复制源码放到游戏目录中的 rpy 脚本文件中或将附件解压至游戏目录
- 调用 position_helper 屏幕
使用示范:
- 在界面中调用
[RenPy] 纯文本查看 复制代码 screen test():
add mao align (0.5, 1.0)
use position_helper() - 在脚本标签中使用
[RenPy] 纯文本查看 复制代码
show mao mtn_01
call screen position_helper() - 特殊用法:传参任意数量的可视组件于 position_helper 中,则将按传入顺序逐一绘制
|
评分
-
查看全部评分
|