@ferdinand
This is my GeUserArea class
class KidooShotManagerUserArea(gui.GeUserArea):
def __init__(self, dialog=None):
super(KidooShotManagerUserArea, self).__init__()
self.dialog = dialog
# 我自己的库实例
self.GetC4dCont = GetC4DContent()
self.PathPro = PathProcess()
self.GuiUtils = GuiUtils()
self.padding = 8 # 每张图片之间的间距。
self.base_size = 128 # 每张图片的基础大小
self.min_size = 50 # 最小尺寸
self.max_size = 256 # 最大尺寸
self.scale_factor = 1.0 # 缩放因子
self.col_count = 4 # 列数
self.cell_size = self.base_size # 每个格子的实际大小
self.rounded_radius = 10 # 圆角半径
self.assets = [] # 存储图片
self.selected_index = 0 # 用于记录选中卡片
# 基础颜色变量
self.bg_color = c4d.Vector(43/255.0, 43/255.0, 43/255.0) # 背景颜色
self.bg_color_01 = c4d.Vector(20/255.0, 20/255.0, 20/255.0) # 背景颜色
def load_card(self, image_assets: list) -> list:
"""
input:
- image_assets: [{path, name}, {path, name}, ...] 需要输入图片的路径和名称列表。
returns:
- assets: 返回[[bitmap, path, name], ...] 的列表,包含加载的图片对象、路径和名称。
"""
assets = []
for asset in image_assets:
path = asset.get('path')
name = asset.get('name', '')
default_image = os.path.join(_ICONPATH, 'photo-film_white.png')
bitmap = None
try:
if os.path.exists(path):
# 如果路径存在,尝试加载图片
bitmap = self.GuiUtils.load_bitmap(path)
else:
# 如果路径不存在,尝试加载默认图片(注:确保默认图片可以被正确加载)
bitmap = self.GuiUtils.load_bitmap(default_image)
except Exception as e:
bitmap = self.GuiUtils.load_bitmap(default_image)
assets.append([bitmap, path, name])
self.assets = assets
self.Redraw()
return assets
def GetMinSize(self):
"""
动态计算最小尺寸,基于卡片数量和布局参数
"""
if not self.assets:
return 0, 100 # 没有资产时返回最小高度
# 获取当前区域宽度(如果还没有初始化,使用默认值)
area_width = self.GetWidth() if hasattr(self, 'GetWidth') and self.GetWidth() > 0 else 400
# 计算字体高度
self.DrawSetFont(c4d.FONT_DEFAULT)
text_height = self.DrawGetFontHeight()
# 计算网格布局
cols, cell = self._calc_grid(
area_width,
self.base_size,
self.padding,
self.min_size,
self.max_size,
self.scale_factor
)
# 计算总行数
total_cards = len(self.assets)
rows = (total_cards + cols - 1) // cols # 向上取整
# 计算总高度
total_cell_height = cell + text_height + self.padding
total_height = (rows * (total_cell_height + self.padding)) + self.padding
# 确保最小高度
min_height = max(int(total_height), 100)
return 0, min_height
def _calc_grid(
self,
width: int,
base_size: int,
padding: int,
min_size: int,
max_size: int,
scale_factor: float = 1.0,
mode: str = "auto",
fixed_cols: int = None,
fixed_cell: int = None
):
"""
根据区域宽度和配置计算网格列数与 cell 尺寸
:param width: 总区域宽度
:param base_size: 基础尺寸(设计稿默认卡片大小)
:param padding: 内边距
:param min_size: 允许的最小尺寸
:param max_size: 允许的最大尺寸
:param scale_factor: 缩放因子(用户滑块控制,默认1.0)
:param mode: "auto" | "fixed_cols" | "fixed_cell"
:param fixed_cols: 固定列数
:param fixed_cell: 固定格子大小
"""
if mode == "fixed_cols" and fixed_cols is not None:
cols = fixed_cols
cell = (width - (cols + 1) * padding) / cols
return cols, max(min(cell, max_size), min_size)
if mode == "fixed_cell" and fixed_cell is not None:
cols = max(1, int((width + padding) / (fixed_cell + padding)))
return cols, fixed_cell
# --- 改进的 auto 模式 ---
# 用 scale_factor 控制缩放
scaled_size = base_size * scale_factor
cell = max(min(scaled_size, max_size), min_size)
cols = max(1, int((width + padding) / (cell + padding)))
return cols, cell
def _get_cell_rect(
self,
index: int,
area_width: int,
base_size: int,
padding: int,
min_size: int,
max_size: int,
text_height: int,
scale_factor: float = 1.0,
mode: str = "auto",
fixed_cols: int = None,
fixed_cell: int = None
):
"""
获取指定 index 的 cell 坐标区域 (含文字区域)
"""
cols, cell = self._calc_grid(
area_width,
base_size,
padding,
min_size,
max_size,
scale_factor,
mode,
fixed_cols=fixed_cols,
fixed_cell=fixed_cell
)
total_cell_height = cell + text_height + padding
row = index // cols
col = index % cols
px = padding + col * (cell + padding)
py = padding + row * (total_cell_height + padding)
rect_x1 = px
rect_y1 = py
rect_x2 = px + cell
rect_y2 = py + cell + text_height + padding
return int(rect_x1), int(rect_y1), int(rect_x2), int(rect_y2)
def set_cell_size(self, size: int = None):
if size is None:
size = self.base_size
self.scale_factor = size
self.Redraw()
def DrawMsg(self, x1, y1, x2, y2, msg):
# 绘制背景
self.SetClippingRegion(x1, y1, x2, y2) # 设置裁剪区域
self.OffScreenOn() # 开启屏幕裁剪
self.DrawSetPen(c4d.COLOR_BG) #设置背景颜色
self.DrawRectangle(x1, y1, x2, y2) # 绘制背景
self.DrawSetPen(1) #设置背景颜色
self.DrawSetFont(c4d.FONT_DEFAULT)
text_height = self.DrawGetFontHeight()
for index, (bitmap, _, name) in enumerate(self.assets):
image_orig_width = bitmap.GetBw()
image_orig_height = bitmap.GetBh()
# 获取绘制位置
# 获取绘制位置(当前索引 index 对应卡片的矩形区域)
px, py, px2, py2 = self._get_cell_rect(
index=index, # 当前要绘制的卡片索引
area_width=self.GetWidth(), # 当前用户区域的总宽度(UI 绘制区域宽度)
base_size=self.base_size, # 期望的基础 cell 大小(默认 128px)
padding=self.padding, # cell 之间的间距(默认 8px)
min_size=self.min_size, # cell 允许的最小尺寸(避免过小)
max_size=self.max_size, # cell 允许的最大尺寸(避免过大)
text_height=text_height, # 当前字体的高度,用于计算文字占位
scale_factor=self.scale_factor, # 当前缩放因子(用户滑块控制,默认 1.0)
)
# 绘制背景
self.DrawSetPen(self.bg_color_01)
self.DrawRectangle(px, py, px2, py2)
cols, actual_cell = self._calc_grid(
self.GetWidth(),
self.base_size,
self.padding,
self.min_size,
self.max_size,
self.scale_factor
)
cell = int(actual_cell)
# 缩放计算
scale = min(cell / image_orig_width, cell / image_orig_height)
dw, dh = int(image_orig_width * scale), int(image_orig_height * scale)
ox = px + (cell - dw) // 2
oy = py + (cell - dh) // 2
# 鼠标选中边缘高光绘制
if index == self.selected_index:
border_thickness = 2 # 边框线宽,可调整
border_color = c4d.Vector(1.0, 1.0, 1.0) # 例如白色边框,可根据需要更改颜色
self.DrawSetPen(border_color) # 设置绘制颜色为高亮边框颜色
# 绘制矩形边框,范围比卡片区域每边扩大2像素
expand = border_thickness + 2
self.DrawFrame(px - expand, py - expand, px2 + expand, py2 + expand, lineWidth=border_thickness)
# 绘制图片背景
self.DrawSetPen(1001)
self.DrawRectangle(px, py, px + cell, py + cell)
# 绘制图片
self.DrawBitmap(bitmap, ox, oy, dw, dh, 0, 0, image_orig_width, image_orig_height,
c4d.BMP_ALLOWALPHA | c4d.BMP_NORMALSCALED)
# 绘制文字
text_width = self.DrawGetTextWidth(name)
text_x = px + cell // 2 - text_width // 2
text_y = py + cell + self.padding // 2
self.DrawSetTextCol(1, self.bg_color_01)
self.DrawSetFont(c4d.FONT_DEFAULT | c4d.FONT_BIG | c4d.FONT_BIG_BOLD)
self.DrawText(name, text_x, text_y, c4d.DRAWTEXT_STD_ALIGN)
def InputEvent(self, msg):
# 判断事件是否为鼠标滚轮,并且有修饰键按下(QUALIFIER,例如 Ctrl/Shift)
if msg[c4d.BFM_INPUT_CHANNEL] == c4d.BFM_INPUT_MOUSEWHEEL and msg[c4d.BFM_INPUT_QUALIFIER]:
delta = msg.GetInt32(c4d.BFM_INPUT_VALUE) # 获取滚轮的数值(正=向上,负=向下)
size = self.scale_factor # 当前缩放因子
# 根据滚轮方向调整缩放因子
if delta < 0:
size -= 0.05 # 滚轮向下 → 缩小
else:
size += 0.05 # 滚轮向上 → 放大
# 限制缩放范围在 [0.4, 2.0] 之间
size = max(0.4, min(2.0, size))
# 只有在新值和旧值不同的时候才更新(避免重复刷新)
if size != self.scale_factor:
self.scale_factor = size
self.set_cell_size(self.scale_factor) # 应用新的缩放因子到 UI
if msg[c4d.BFM_INPUT_DEVICE] == c4d.BFM_INPUT_MOUSE \
and msg[c4d.BFM_INPUT_CHANNEL] == c4d.BFM_INPUT_MOUSELEFT \
and msg[c4d.BFM_INPUT_VALUE]:
local_x = msg[c4d.BFM_INPUT_X]
local_y = msg[c4d.BFM_INPUT_Y]
scroll_y = self.dialog.get_scroll_offset()
actual_x = local_x
actual_y = local_y + scroll_y
self.on_click(actual_x, actual_y)
return True
def on_click(self, x, y):
self.DrawSetFont(c4d.FONT_DEFAULT)
text_height = self.DrawGetFontHeight()
for index, (_, _, name) in enumerate(self.assets):
# 使用完整的 _get_cell_rect 公式来计算卡片矩形
px, py, px2, py2 = self._get_cell_rect(
index=index, # 当前卡片索引
area_width=self.GetWidth(), # 用户区域宽度
base_size=self.base_size, # 基础 cell 大小(默认 128px)
padding=self.padding, # cell 间距
min_size=self.min_size, # 最小 cell 尺寸
max_size=self.max_size, # 最大 cell 尺寸
text_height=text_height, # 文字高度(需要在类里存好)
scale_factor=self.scale_factor # 缩放因子(用户滑动控制)
)
# 判断点击坐标是否在当前卡片矩形内
if px <= x <= px2 and py <= y <= py2:
self.selected_index = index
self.Redraw() # 触发重绘,显示选中效果
# 发送点击消息给父级
magContainer = BaseContainer(ID_MSG_KIDOO_SHOT_MANAGER_IMAGE_CLICKED)
magContainer.SetInt32(0, index)
magContainer.SetString(1, name)
self.SendParentMessage(magContainer)
break