Skip to content

Instantly share code, notes, and snippets.

@NiklasRosenstein
Last active January 22, 2019 17:02
Show Gist options
  • Save NiklasRosenstein/6249168 to your computer and use it in GitHub Desktop.
Save NiklasRosenstein/6249168 to your computer and use it in GitHub Desktop.
IconButton class for the Python Cinema 4D API.
# 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)
@soupman99
Copy link

What do these lines do? When I remove them the user areas disappear.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment