找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 42|回复: 1

[原创] 使用分割线布局来调整UI

[复制链接]
发表于 前天 17:33 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
仍然是某天晚上, 一体机问我能不能写一个分割线来分割两个组件并可以拖动这个分割线来调整布局

考虑到如果真的写一个分割线控制父组件的话需要代码注入这种危险的技术---

我选择写了一个新的布局方式, 这个布局方式内置了一条分割线用于调整组件, 因为是自己写的所以性能开销会很小, 效果也相当不错


19697CC077A642023B0AB58A38F851CA.png
那么话不多说, 下面是代码:
[RenPy] 纯文本查看 复制代码


"""renpy

python early:
"""
from renpy.display.render import render
import pygame


class SplitLayout(renpy.display.layout.Container):
    def __init__(self, 
            horizontal_nor_vertical=True, 
            default_percent=0.5, 
            divider_idle=(144, 144, 144), 
            divider_hover=(100, 160, 210), 
            divider_select=(90, 180, 255),
            max_percent = 0.9,
            min_percent = 0.1,
            divider_width = 3,
            divider_border = 10,
            render_limit = True,
            event_limit = True,
            divider_draggable = True,
            *args, **kwargs
        ):
        super().__init__(*args, **kwargs)
        self.default_percent = default_percent
        self.divider_idle = divider_idle
        self.divider_hover = divider_hover
        self.divider_select = divider_select
        self.divider_width = divider_width
        self.divider_border = divider_border
        self.render_limit = render_limit
        self.event_limit = event_limit
        self.divider_draggable = divider_draggable
        self.min_percent = min_percent
        self.max_percent = max_percent
        self.direction = (horizontal_nor_vertical, not horizontal_nor_vertical)

        self.draggable = True
        
        self.divider_state = 0
        self.divider_pos = None
        self.render_size = (0, 0)

    def render(self, width, height, st, at):
        rv = renpy.Render(width, height)
        
        default_percent = tuple(i * self.default_percent for i in self.direction)

        self.offsets = self._list_type()
        self.render_size = (width, height)

        a_render_size = width * (default_percent[0] if self.direction[0] else 1.0), height * (default_percent[1] if self.direction[1] else 1.0)
        b_render_size = width * ((1 - default_percent[0]) if self.direction[0] else 1.0), height * ((1 - default_percent[1]) if self.direction[1] else 1.0)

        if len(self.children) >= 1:
            render_a = render(self.children[0], *a_render_size, st, at)

            rv.blit(render_a, (0, 0))

            if self.render_limit:
                offset = self.children[0].place(rv, 0, 0, *a_render_size, render_a)
            else:
                offset = self.children[0].place(rv, 0, 0, width, height, render_a)
                
            self.offsets.append(offset)
            

        if len(self.children) >= 2:
            render_b = render(self.children[1], *b_render_size, st, at)
            b_pos = a_render_size[0] if self.direction[0] else 0.0, a_render_size[1] if self.direction[1] else 0.0

            if self.render_limit:
                offset = self.children[1].place(rv, *b_pos, *b_render_size, render_b)
            else:
                offset = self.children[1].place(rv, *b_pos, width, height, render_b)
                
            self.offsets.append(offset)

        self.divider_pos = (width * default_percent[0], height * default_percent[1])
        divider_end_pos = (width * default_percent[0] if self.direction[0] else width, height * default_percent[1] if self.direction[1] else height)
        
        color = self.divider_select if self.divider_state == 2 else self.divider_hover if self.divider_state == 1 else self.divider_idle

        rv.canvas().line(color = color, start_pos=self.divider_pos, end_pos=divider_end_pos, width=self.divider_width)

        return rv

    def event(self, ev, x, y, st):
        if self.divider_draggable:
            if ev.type == pygame.MOUSEMOTION and self.divider_pos is not None:
                if self.divider_state == 2:
                    self.default_percent = x / self.render_size[0] if self.direction[0] else y / self.render_size[1]
                    if self.default_percent < self.min_percent: self.default_percent = self.min_percent
                    if self.default_percent > self.max_percent: self.default_percent = self.max_percent
                    renpy.redraw(self, 0)
                    raise renpy.display.core.IgnoreEvent()
                else:

                    divider_hover_range = (
                        (-self.divider_border, +self.divider_border) if self.direction[0] > 0 else (0, 0+self.render_size[0]),
                        (-self.divider_border, +self.divider_border) if self.direction[1] > 0 else (0, 0+self.render_size[1]))
                    old_state = self.divider_state

                    self.divider_state = (divider_hover_range[0][0] < x - self.divider_pos[0] < divider_hover_range[0][1] and divider_hover_range[1][0] < y - self.divider_pos[1] < divider_hover_range[1][1] )
                    
                    if old_state != self.divider_state:
                        renpy.redraw(self, 0)

            if ev.type == pygame.MOUSEBUTTONDOWN and self.divider_state == 1 and ev.button == 1:
                self.divider_state = 2
                renpy.redraw(self, 0)
                raise renpy.display.core.IgnoreEvent()
            
            if ev.type == pygame.MOUSEBUTTONUP and self.divider_state == 2 and ev.button == 1:
                self.divider_state = 1
                renpy.redraw(self, 0)
                raise renpy.display.core.IgnoreEvent()

        b_pos = (self.render_size[0] * self.default_percent if self.direction[0] else 0.0, self.render_size[1] * self.default_percent if self.direction[1] else 0.0)

        if (x > b_pos[0] and self.direction[0]) or (y > b_pos[1] and self.direction[1]):
            if len(self.children) >= 2:
                print("B", self.children[1], st)
                self.children[1].event(ev, x - b_pos[0], y - b_pos[1], st)
            if len(self.children) >= 1 and self.event_limit is False:
                print("A", self.children[0], st)
                self.children[0].event(ev, x, y, st)
        else:
            if len(self.children) >= 1:
                print("A", self.children[0], st)
                self.children[0].event(ev, x, y, st)
            if len(self.children) >= 2 and self.event_limit is False:
                print("B", self.children[1], st)
                self.children[1].event(ev, x - b_pos[0], y - b_pos[1], st)

    def add(self, child):
        if child is None or child in self.children:
            return
        
        if len(self.children) < 2:
            self.children.append(renpy.displayable(child))
        else:
            if child != self.children[0] and child != self.children[1]:
                raise Exception("Children is Out of Range\nNow Child: " + str(self.children[0]) + " - ", str(self.children[1]), "\nAdd in child:", str(child))


renpy.register_sl_displayable("split", SplitLayout, "", 2

    ).add_property("divider_width"  # 条渲染的宽度
    ).add_property("divider_border"  # 条判定的宽度
    ).add_property("divider_draggable"  # 条是否可拖动

    ).add_property("divider_idle"  # 条的 idle 颜色
    ).add_property("divider_hover"  # 条的 hover 颜色
    ).add_property("divider_select"  # 条的 select 颜色

    ).add_property("max_percent"  # 最大的百分度
    ).add_property("min_percent"  # 最小的百分度
    ).add_property("default_percent"  # 默认的百分度

    ).add_property("render_limit"  # 是否按照分割后的大小渲染子组件 (不然就以 SplitLayout 的大小进行渲染)
    ).add_property("event_limit"  # 是否只把 event 传入一个子组件 (不然同一个 event 能被两个子组件接收)
    ).add_property("horizontal_nor_vertical"  # 纵或横向布局
    )



将以上代码保存到一个 _ren.py 文件里就可以使用了 ( 我的命名是 _split_layout_ren.py )

像上图中显示的效果, 代码如下:
[RenPy] 纯文本查看 复制代码

screen main_menu():
    split:
        default_percent 0.3
        fixed: 
            use about

        split:
            default_percent 0.4

            split:
                default_percent 0.7
                horizontal_nor_vertical False

                fixed:
                    use preferences
                
                fixed:
                    use history
            
            fixed: 
                use help



此组件的相关参数在 add_property 里写的很明白了, 想必都会用吧 (

注:
    此组件只能接受两个子组件
    有些 screen 会传入多个子组件, 这个时候需要一个 fixed 来将其限制为同一个组件
    图片里的 game_menu 被我修改过, 去掉了左边的 navigation 于对应的占位空间
发表于 前天 23:47 | 显示全部楼层
效果看起来好高级!!
回复 支持 抱歉

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|RenPy中文空间 ( 苏ICP备17067825号|苏公网安备 32092302000068号 )

GMT+8, 2025-4-8 10:40 , Processed in 0.050278 second(s), 28 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表