Instantly share code, notes, and snippets.
Last active
January 22, 2019 17:02
-
Star
1
(1)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save NiklasRosenstein/6249168 to your computer and use it in GitHub Desktop.
IconButton class for the Python Cinema 4D API.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright (C) 2013, Niklas Rosenstein | |
# http://niklasrosenstein.de/ | |
# | |
# This work is free. You can redistribute it and/or modify it under the | |
# terms of the Do What The Fuck You Want To Public License, Version 2, | |
# as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. | |
# | |
# Changelog: | |
# | |
# 1.1: Corrected action msg for Command() method, information like qualifiers | |
# and other input event information should now be correctly received in | |
# the Command() method. | |
# 1.2: Changed license to WTFPL, do what the fuck you want with it! | |
# 1.3: Thanks to Jet Kawa to tell me about the flickering when holding and | |
# dragging. As he suggested correctly, OffScreenOn() fixes this. | |
# 1.4: Added mouse hover feature | |
# 1.5: Fixed drawing algorithm to correctly update only parts of the user | |
# area. The x1, y1, x2, y2 only reflect the part of the user area | |
# that is being redrawn, not the actual dimensions of the area. Since | |
# these were used to caclculate width and height of the area, rendering | |
# issues occured when only parts of the user area were updated (eg. | |
# in a scroll group). | |
# The standard button has been renamed to "Close" and closes the dialog | |
# when clicked. | |
import time | |
import c4d | |
from c4d.gui import GeUserArea, GeDialog | |
class IconButton(GeUserArea): | |
VERSION = (1, 4) | |
M_NOICON = 0 | |
M_ICONLEFT = 1 | |
M_ICONRIGHT = 2 | |
M_FULL = 3 | |
C_TEXT = c4d.COLOR_TEXT | |
C_BG = c4d.COLOR_BGEDIT | |
C_HIGHLIGHT = c4d.COLOR_BGFOCUS | |
C_BGPRESS = c4d.COLOR_BG | |
S_ICON = 24 | |
S_PADH = 4 | |
S_PADV = 4 | |
def __init__(self, paramid, text, icon, mode=M_ICONLEFT): | |
super(IconButton, self).__init__() | |
self.paramid = paramid | |
self.text = text | |
self.icon = icon | |
self.mode = mode | |
self.pressed = False | |
self.last_t = -1 | |
self.mouse_in = False | |
self.interval = 0.2 | |
def _CalcLayout(self): | |
text_x = self.S_PADH | |
text_w = self.DrawGetTextWidth(str(self.text)) | |
text_h = self.DrawGetFontHeight() | |
icon_x = self.S_PADH | |
width = text_w + self.S_PADH * 2 | |
height = max([text_h, self.S_ICON]) + self.S_PADV * 2 | |
draw_icon = True | |
if self.mode == self.M_ICONLEFT: | |
icon_x = self.S_PADH | |
text_x = self.S_PADH + self.S_ICON + self.S_PADH | |
width += self.S_ICON + self.S_PADH | |
elif self.mode == self.M_ICONRIGHT: | |
icon_x = self.GetWidth() - (self.S_PADH + self.S_ICON) | |
text_x = self.S_PADH | |
width += self.S_ICON + self.S_PADH | |
else: | |
draw_icon = False | |
return locals() | |
def _DrawIcon(self, icon, x1, y1, x2, y2): | |
# Determine if the icon is a simple color. | |
if not icon: | |
pass | |
if isinstance(icon, (int, c4d.Vector)): | |
self.DrawSetPen(icon) | |
self.DrawRectangle(x1, y1, x2, y2) | |
# or if it is a bitmap icon. | |
elif isinstance(icon, c4d.bitmaps.BaseBitmap): | |
self.DrawBitmap(icon, x1, y1, (x2 - x1), (y2 - y1), | |
0, 0, icon.GetBw(), icon.GetBh(), c4d.BMP_ALLOWALPHA) | |
else: | |
return False | |
return True | |
def _GetHighlight(self): | |
delta = time.time() - self.last_t | |
return delta / self.interval | |
def _GetColor(self, v): | |
if isinstance(v, c4d.Vector): | |
return v | |
elif isinstance(v, int): | |
d = self.GetColorRGB(v) | |
return c4d.Vector(d['r'], d['g'], d['b']) ^ c4d.Vector(1.0 / 255) | |
else: | |
raise TypeError('Unexpected value of type %s' % v.__class__.__name__) | |
def _InterpolateColors(self, x, a, b): | |
if x < 0: x = 0.0 | |
elif x > 1.0: x = 1.0 | |
a = self._GetColor(a) | |
b = self._GetColor(b) | |
return a * x + b * (1 - x) | |
# GeUserArea Overrides | |
def DrawMsg(self, x1, y1, x2, y2, msg): | |
self.OffScreenOn() # Double buffering | |
# Draw the background color. | |
bgcolor = self.C_BG | |
if self.pressed: | |
bgcolor = self.C_BGPRESS | |
elif self.mode == self.M_FULL and self.icon: | |
bgcolor = self.icon | |
else: | |
h = self._GetHighlight() | |
ca, cb = self.C_HIGHLIGHT, self.C_BG | |
if not self.mouse_in: | |
ca, cb = cb, ca | |
# Interpolate between these two colors. | |
bgcolor = self._InterpolateColors(h, ca, cb) | |
w, h = self.GetWidth(), self.GetHeight() | |
self._DrawIcon(bgcolor, 0, 0, w, h) | |
# Determine the drawing position and size of the | |
# colored icon and the text position. | |
layout = self._CalcLayout() | |
if layout['draw_icon']: | |
x = layout['icon_x'] | |
y = min([h / 2 - self.S_ICON / 2, self.S_PADV]) | |
# Determine if the icon_DrawIcon | |
self._DrawIcon(self.icon, x, y, x + self.S_ICON, y + self.S_ICON) | |
if 'draw_text': | |
self.DrawSetTextCol(self.C_TEXT, c4d.COLOR_TRANS) | |
x = layout['text_x'] | |
y = max([h / 2 - layout['text_h'] / 2, self.S_PADV]) | |
self.DrawText(str(self.text), x, y) | |
def GetMinSize(self): | |
layout = self._CalcLayout() | |
return layout['width'], layout['height'] | |
def InputEvent(self, msg): | |
device = msg.GetLong(c4d.BFM_INPUT_DEVICE) | |
channel = msg.GetLong(c4d.BFM_INPUT_CHANNEL) | |
catched = False | |
if device == c4d.BFM_INPUT_MOUSE and channel == c4d.BFM_INPUT_MOUSELEFT: | |
self.pressed = True | |
catched = True | |
# Poll the event. | |
tlast = time.time() | |
while self.GetInputState(device, channel, msg): | |
if not msg.GetLong(c4d.BFM_INPUT_VALUE): break | |
x, y = msg.GetLong(c4d.BFM_INPUT_X), msg.GetLong(c4d.BFM_INPUT_Y) | |
map_ = self.Global2Local() | |
x += map_['x'] | |
y += map_['y'] | |
if x < 0 or y < 0 or x >= self.GetWidth() or y >= self.GetHeight(): | |
self.pressed = False | |
else: | |
self.pressed = True | |
# Do not redraw all the time, this would be useless. | |
tdelta = time.time() - tlast | |
if tdelta > (1.0 / 30): # 30 FPS | |
tlast = time.time() | |
self.Redraw() | |
if self.pressed: | |
# Invoke the dialogs Command() method. | |
actionmsg = c4d.BaseContainer(msg) | |
actionmsg.SetId(c4d.BFM_ACTION) | |
actionmsg.SetLong(c4d.BFM_ACTION_ID, self.paramid) | |
self.SendParentMessage(actionmsg) | |
self.pressed = False | |
self.Redraw() | |
return catched | |
def Message(self, msg, result): | |
if msg.GetId() == c4d.BFM_GETCURSORINFO: | |
if not self.mouse_in: | |
self.mouse_in = True | |
self.last_t = time.time() | |
self.SetTimer(30) | |
self.Redraw() | |
return super(IconButton, self).Message(msg, result) | |
def Timer(self, msg): | |
self.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSELEFT, msg) | |
g2l = self.Global2Local() | |
x = msg[c4d.BFM_INPUT_X] + g2l['x'] | |
y = msg[c4d.BFM_INPUT_Y] + g2l['y'] | |
# Check if the mouse is still inside the user area or not. | |
if x < 0 or y < 0 or x >= self.GetWidth() or y >= self.GetHeight(): | |
if self.mouse_in: | |
self.mouse_in = False | |
self.last_t = time.time() | |
h = self._GetHighlight() | |
if h < 1.0: | |
self.Redraw() | |
elif not self.mouse_in: | |
self.Redraw() | |
self.SetTimer(0) | |
def AddIconButton(dialog, paramid, text, icon, mode=IconButton.M_ICONLEFT, | |
auto_store=True): | |
r""" | |
Creates an Icon Button on the passed dialog. | |
""" | |
ua = IconButton(paramid, text, icon, mode) | |
if auto_store: | |
if not hasattr(dialog, '_icon_buttons'): | |
dialog._icon_buttons = [] | |
dialog._icon_buttons.append(ua) | |
dialog.AddUserArea(paramid, c4d.BFH_SCALEFIT) | |
dialog.AttachUserArea(ua, paramid) | |
return ua | |
# Example | |
# ======= | |
class Dialog(GeDialog): | |
def __init__(self): | |
super(Dialog, self).__init__() | |
# GeDialog Overrides | |
def CreateLayout(self): | |
self.SetTitle("Dialog") | |
self.ScrollGroupBegin(9000, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, c4d.SCROLLGROUP_HORIZ | c4d.SCROLLGROUP_VERT, 0, 0) | |
self.GroupBegin(0, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0) | |
# Create a small example text + number field. | |
self.GroupBegin(1000, c4d.BFH_SCALEFIT, 0, 1, "", 0, 0) | |
self.AddStaticText(1001, 0, name="Value") | |
self.AddEditNumberArrows(1002, 0) | |
self.GroupEnd() | |
# Create six different IconButton's | |
self.GroupBegin(2000, c4d.BFH_SCALEFIT, 3, 0, "", 0, 0) | |
# With an icon. | |
path = 'C:/Users/niklas/Desktop/foo.png' | |
bmp = c4d.bitmaps.BaseBitmap() | |
bmp.InitWith(path) | |
AddIconButton(self, 2001, "Image Icon Left", bmp, IconButton.M_ICONLEFT) | |
AddIconButton(self, 2002, "Image Icon Right", bmp, IconButton.M_ICONRIGHT) | |
AddIconButton(self, 2003, "No Icon Button", None, IconButton.M_NOICON) | |
# With a color. | |
color = c4d.Vector(0.2, 0.5, 0.3) | |
AddIconButton(self, 2004, "Color Left", color, IconButton.M_ICONLEFT) | |
AddIconButton(self, 2005, "Color Right", color, IconButton.M_ICONRIGHT) | |
AddIconButton(self, 2006, "Color Full", c4d.COLOR_TEXTFOCUS, IconButton.M_FULL) | |
self.GroupEnd() | |
# Create a button at the bottom of the dialog- | |
self.GroupBegin(3000, c4d.BFH_SCALEFIT, 0, 1, "", 0, 0) | |
self.AddStaticText(3001, c4d.BFH_SCALEFIT, name="") # Filler | |
self.AddButton(3002, c4d.BFH_RIGHT, name="Close") | |
self.GroupEnd() | |
self.GroupEnd() | |
self.GroupEnd() # Scroll Group | |
return True | |
def Command(self, paramid, msg): | |
if paramid == 3002: | |
self.Close() | |
print "Button with ID", paramid, "pressed." | |
return True | |
dlg = Dialog() | |
dlg.Open(c4d.DLG_TYPE_MODAL_RESIZEABLE, defaultw=180) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What do these lines do? When I remove them the user areas disappear.
ua = IconButton(paramid, text, icon, mode)