UI Tool Preview - The draw (DrawApi) module



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 15/08/2011 at 12:13, xxxxxxxx wrote:

    draw (DrawApi) - Preview
    -----------------------

    NOTE: The module is now called draw, not DrawAPI any more. The example code of the examples shown here is from the old prototype. It is now even much more handy to use. I hope to finish it soon.

    Imagine you'd need to create a dynamic Graphical User Interface to show a list of items, like Cinema 4Ds Object Manager for example. It would be a pain for you to subclass the GeUserArea class and to all the drawing stuff, because it's so linear and intransparent.
    This is the place where DrawAPI comes in place. It's a module providing object-orientated handling of elements for a User Interface.

    The most important class is the View. A View defines a rectangular area on the User Area supporting a backgroundcolor, input events and even subviews.

    A views position and size is defined by a Frame, which can carry relative or absolute x, y, width and height values, customizeable for each.

    A relative position is always calculated to it's masters absolute position and size. This doesn't mean a View with relative Frame-values does need a superview with only absolute ones. The absolute values are calculated at rendertime allowing you to do any relative bahaviour you want.

    There is also a special subclass of a GeUserArea named DrawArea which does already implement the rendering of Views, so you don't even need to handle that.

    Let's just create a User Interface with a blue blackground and a View with another Subview on it, just like this hierarchy and properties.
    The little r's and a's before a number tells you that the value is either relative or absolute.

    DrawArea            Color blue  
    \+ - View            Position a10, a10   Size    r0.5, r0.5  Color red  
    \+ - + - View        Position r0,  r0    Size    a20,  r1    Color yellow
    

    The resulting Interface will look like this:

    We can see that the red area is moved about some pixles (exactly 10) from the blue areas origin (top left) in x and y direction.
    The red area is also only as half as wide and high as the blue one. (Remember that the view is really moved, so the red area overlaps about 10 pixels from the real middle of the blue area.)

    Next thing we see is the yellow area which has it's origin at the same position as the red view, although it's position is 0, 0. That's because it is a subview of the red one and it's position and size is relative to this one. This also leads to that the yellow area is as high as the red one,  but is only 20 pixels wide.

    And this is the simple code to achieve this.

    import  c4d  
    import  DrawAPI     as d  
      
    COLOR_BLUE  = c4d.Vector(.3, .4, .8)  
    COLOR_RED   = c4d.Vector(.8, .25, .15)  
    COLOR_YELLOW= c4d.Vector(.8, .7, .3)  
      
    def main() :  
      area        = d.DrawArea()  
      area.color  = COLOR_BLUE            # make the DrawArea's backgroundcolor blue  
      
      
      # create a View with our desired properties  
      view1       = d.View(10, 10, .5, .5, abs_x = True, abs_y = True)  
      view1.color = COLOR_RED  
      
      area.AddSubview(view1)  # make view1 a subview of the DrawArea  
      
      # create the second View with our desired properties  
      view2       = d.View(0, 0, 20, 1, abs_w = True)  
      view2.color = COLOR_YELLOW  
      
      view2.AddSubview(view2) # make view2 a subview of the first view  
      
      dlg         = d.TestDialog(area)    # a dialog just showing a GeUserArea  
      dlg.Open()  # by default c4d.DLG_TYPE_MODAL_RESIZEABLE  
      
    main()
    

    The code is actually for the Script manager. That is the best way to create and test interfaces for the DrawAPI, because you can get immediate feedback when clicking on execute.

    Now, what's with the example from above, the Interface with the list of items ? No problem. DrawAPI comes with a class for easy and efficient creating such, called the ListView.
    The ListView does actually nothing more than creating subviews that are the items in the list.
    But those Views must be a special subclass of (or provide the same interface as) ListViewRow. It is important that those Views provide a GetHeight() method, otherwise the ListView wouldn't know where to place the next row.
    Also, the ListView needs to know how many items to display, etc. This is why it also needs an instance of ListViewSource (or an object that provides the same interface).

    Let's just show you an example step-by-step. This will also introduce another class, the Label, which does nothing more than showing text.

    The resulting Interface will look like this:

    First thing we need to do is to decide if we subclass the ListViewRow to apply our desired behaviour or to just add a Label as subview. I decide to subclass it, as we need to override the ListViewRow.Update() method.

    This is actually very simple, because we only need to add a Label to the Row and override the Update method to be able to modify our row's label's text.

    import  c4d  
    import  DrawAPI     as d  
      
    COLOR_GREY1 = c4d.Vector(.21)  
    COLOR_GREY2 = c4d.Vector(.23)  
    COLOR_WHITE = c4d.Vector(.85)  
      
    class TextRow(d.ListViewRow) :  
      
      def OnInit(self) :  
          'Called on initialization to bypass overriding __init__.'  
          # create a label with (yet) no text and an absolute position of 5, 2  
          # (we don't want the label to change it's position when the userarea resizes)  
          # note that the Label-constructor does not take any argument defining the size.  
          # a label does only have a position.  
          self.label  = d.Label('', 5, 2, abs_x = True, abs_y = True) # text, x, y  
          self.label.textcolor    = COLOR_WHITE   # set the textcolor to white  
          self.AddSubview(self.label) # otherwise the label wouldn't be rendered  
      
          # we also want to have our view to be 17 pixels high  
          # note: you can override GetHeight, but the default implementation does already  
          # return the value of self.frame.size.y  
          self.frame.size.y   = 17  
      
      def Update(self, text, index) :  
          '''  
          This method is called in order to update some content within the row.  
          We can define the arguments in whatever way we want, because the arguments  
          passed to this method are defined in the ListViewSource.GetUpdateData() method.  
          We also pass the index of the row to this method, because we want to set the  
          background color according to this value, so the rows differ from each other.  
          '''  
          self.label.text = text  
      
          # set backgroundcolor  
          if index % 2:  
              self.color  = COLOR_GREY1  
          else:  
              self.color  = COLOR_GREY2
    

    Next thing is to define our source for the ListView.

    class TextSource(d.ListViewSource) :  
      
      def __init__(self, *strings) :  
          self._data  = strings  
      
      def GetCount(self) :  
          'Called in order to get the number of items in the list.'  
          return len(self._data)  
      
      def MakeRow(self, index) :  
          'Called in order to recieve a view that is placed in the list.'  
          # the default constructor of ListViewRow does not take any arguments.  
          return TextRow()  
      
      def GetUpdateData(self, index) :  
          '''  
          This method needs to return arguments and keyword-arguments passed  
          to ListViewRow.Update().  
          In our case, we need to return the value of the text-argument and  
          the row's index.  
          '''  
          return (self._data[index], index), {}     # a tuple with the string and the index and an empty dictionary
    

    Now, the only thing left to do is to create a ListView with our Source, a DrawArea and a Dialog.

    def main() :  
      area    = d.DrawArea()  
      
      source  = TextSource('I am the first row.', 'And I am the second one.', 'Could you please be quiet up there ?')  
      list_   = d.ListView(source, 0, 0, 1, 1)    # the source, x, y, width, height  
      list_.ReloadData()  # must be called, otherwise there won't be any rows in the ListView  
      
      area.AddSubview(list_)  
      
      dlg     = d.TestDialog(area)  
      dlg.Open()  
      
    main()
    

    Easy practice, isn't it ?
    Let's make this waaaay cooler ! What about the ability to click on one of the items causing the row the become bigger and revealing new content ?

    A View class does implement a MouseEvent and AnyEvent method. You can guess what method is called when.
    Overriding those will enable us to achieve the desired behaviour.

    class TextRow(d.ListViewRow) :  
      
      HEIGHT_DEFAULT  = 17  
      HEIGHT_EXPANDED = 40  
      
      # ....  
      
      def SetHeight(self, h) :  
          self.frame.size.y   = h  
      
      def MouseEvent(self, area, msg, mouse_global, mouse_local) :  
          'Called whenever the mouse clicks into this view.'  
          # we use ListViewRow.GetHeight() because it's convenient in this case and we  
          # don't need to write self.frame.size.y  
          if self.GetHeight() == self.HEIGHT_DEFAULT:  
              self.SetHeight(self.HEIGHT_EXPANDED)  
          else:  
              self.SetHeight(self.HEIGHT_DEFAULT)  
      
          # we need the listview to tell it there was something changed within it's rows  
          # using ListView.SendReloadMessage(with_update = False) we can do so.  
          # as we only need to refresh the position of the rows and not to call the rows'  
          # Update() method, we omit the call_update argument  
          # Note that we could also call ListView.ReloadData(False), but imagine more than one  
          # row would call this method, the rows would be unnecessarily updated multiple times,  
          self.list_view.SendReloadMessage()  
      
          # also, we need to tell the area to redraw after that input event.  
          # same as with ListView.SendReloadMessage(), we don't call UserArea.Redraw()  
          # directly, because it would lead to highly redundant redraw-traffic if  
          # more than one row would call it.  
          area.SendRedrawMessage()
    

    Now that we've implemented the mouseclick, we could add content to be shown on the new area revealed when clicking, but that's too much for this preview of the DrawAPI. But just to whet your appetite, we could add a subview that is within the hidden area of the row and set it's opaque-attribute to False, so it won't be rendered. When the user clicks and MouseEvent is called, we set it to True.

    The result when clicking on the first and last row, those are expanded to 40 pixels, while the middle row is still at 17 pixels.

    This was (hopefully) an introduction that made you curious about using the DrawAPI.
    Please leave some comments below and ask questions if you have any.

    The DrawAPI will be released soon as a stable release, ready for you to use. However, it will be extended by time and API might change. There also won't be a documentation, but methods are well commented. (Yes, the module will be Open Source).

    Thanks for reading !
    Niklas



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 16/08/2011 at 10:48, xxxxxxxx wrote:

    siiiiick!!!!! another incredibly useful tool from niklas. thanks duder!



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 22/08/2011 at 11:34, xxxxxxxx wrote:

    nice that you 'll find it well. :)
    Here's another Preview of the ListView class and how to use it.
    http://www.youtube.com/watch?v=nFgsw_WW1No



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 30/08/2011 at 15:15, xxxxxxxx wrote:

    Please note that I have edited the first post. If you are interested in creating userinterfaces with userareas, I beg you to read it. It is very detailed and should give you a very good overview about what the DrawAPI is.

    Thank you !
    Niklas



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 18/11/2011 at 09:26, xxxxxxxx wrote:

    My newest achievement: Animations on a GeUserArea !
    Example Video

    Also note that the module will now be called draw , no more DrawApi.

    Cheers !



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 19/11/2011 at 04:29, xxxxxxxx wrote:

    Impressive work! The animation stuff is fantastic!



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 02/12/2011 at 14:05, xxxxxxxx wrote:

    Thanks 990adjustments.

    The release is in front of our doors, but not yet. :) Here is the link to the package index on pypi, you can also find the so-far documentation there!

    draw 0.0.71 - PyPi



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 17/02/2012 at 08:35, xxxxxxxx wrote:

    hey Niklas,

    VERY NICE STUFF

    Is this implemented into C4D? I tried one of your code snippets and it gave an error trying to import the draw module.

    I really like the direction you are going with this :) As a newbie to python coding, I have found the grid-like nature of the dialogs to be frustrating. I can code HTML and CSS fairly well and my first bite into python for C4D made me feel like being confined to tables in HTML haha.

    Maybe there is a way to do floating/self adjusting content in dialogs that I just dont know about yet because i am so new to all of this. I have my first plugin pretty much polished off, but there are some things I wish I could do with the interface...like having it automatically adjust the interface to fit where the user docks it (ie horizontally or vertically). I essentially have 25 icons acting as buttons and would LOVE it if they could be arranged more dynamically.

    Anyway, congrats on this module...looks awesome!



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 17/02/2012 at 09:08, xxxxxxxx wrote:

    Hei Marc,

    I'm afraid it isn't implemented into Cinema 4D, it is a self-written Python module. It also isn't released yet, I've rewritten the whole package multiple times now because I didn't like the design of the code. Currently it is at a very stable status and I hope to find time and motivation to make it "finished", at least that it can be published.

    Your post definitely motivates me, thank you for your interest! I'm really planning into moving it forward, but school and other projects keeps me on. I will post it when I've come to the point to be able to publish it. 🙂

    Greetings,
    -Niklas



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 17/02/2012 at 09:13, xxxxxxxx wrote:

    Anytime Niklas!

    Like I said, I am a total newbie to all of this. Coding is not my primary area of interest but I see the complete power it can give you over your work and workflow :)

    There is much i need to learn! My new plugin...or at least the "cooler" new version will be out soon :) Keep an eye out for it



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 17/02/2012 at 09:14, xxxxxxxx wrote:

    btw...did your friend get the RenderFrame script I put out yesterday? hopefully it worked well :)


Log in to reply