class checkbox_pref(InstanceMacro): #e rename -- Checkbox(...), with various kinds of args to say what state it uses in different ways? #e make it one of several prefs controls for other types of pref and control #e generalize to all named state -- e.g. see also LocalVariable_StateRef -- permit passing in the stateref? #e get dflt label from stateref?? # note: this was split out of kluge_dragtool_state_checkbox_expr 061214, # extended here into a def (later renamed checkbox_pref_OLDER), then a class prefs_key = Arg(str) label = Arg(Anything) # string or Widget2D dflt = ArgOrOption(bool, False) sbar_text = Option(str, '') use_label = If(call_Expr(lambda label: type(label) == type(""), label), TextRect(label), label) ## was TextRect(label,1,20) use_sbar_text = or_Expr( sbar_text, If(call_Expr(lambda label: type(label) == type(""), label), label, "")) stateref = Instance(PrefsKey_StateRef(prefs_key, dflt)) # note: without Instance here, next line stateref.value says (correctly): ## AssertionError: compute method asked for on non-Instance <PrefsKey_StateRef#47221(a)> var = stateref.value checkbox = If( var, checkbox_image('mac_checkbox_on.png'), checkbox_image('mac_checkbox_off.png'), ) _value = DisplayListChunk( Highlightable( SimpleRow(CenterY(checkbox), CenterY(use_label)), # align = CenterY is nim ## on_press = Set(debug_evals_of_Expr(stateref.value), not_Expr(var) ), #070119 debug_evals_of_Expr - worked on_press=_self.on_press, # the following works too, but I wanted to intercept it to add some py code [070305]: ## Set( stateref.value, not_Expr(var) ), sbar_text=use_sbar_text)) # note: using DisplayListChunk in _value works & is faster [070103] #070124 comment: the order DisplayListChunk( Highlightable( )) presumably means that the selobj # (which draws the highlightable's delegate) doesn't include the displist; doesn't matter much; # that CenterY(use_label) inside might be ok, or might be a bug which is made up for by the +0.5 I'm adding to drawfont2 # in testdraw.py today -- not sure. incr_drawable = Instance( Boxed(CenterY(checkbox), pixelgap=0, bordercolor=gray, borderwidth=2)) # I tried orange as a warning color -- means the checkbox reflects an intention but not yet the reality. # But it was annoyingly too visible. So I'll try gray. # If all colorboxeds are unpopular, then try an image that has a little spray of lines coming from the center, instead. def on_press(self): self.stateref.value = not self.stateref.value # was, in the expr: Set( stateref.value, not_Expr(var) ) ###e revise this code to use self.draw_incrementally once that's refiled into Highlightable ###e def func(self=self): self.incr_drawable.draw() ## self.draw() # includes the label - probably a waste but who cares self.env.glpane.swapBuffers() # update display [needed] ran_already_flag, funcres = self.run_OpenGL_in_local_coords( func) # this method runs in the Highlightable made in _value assert ran_already_flag return pass # end of class checkbox_pref
class _height_dragger_3(DelegatingInstanceOrExpr): # args height_ref = Arg(StateRef, doc = "stateref to a height variable") direction = Arg(Vector) sbar_text = Option(str, "_height_dragger_3") range = Option(tuple_Expr, None, doc = "range limit of height") # appearance/behavior #e should draw some "walls" too, and maybe limit the height drag_handler = Instance( DragBehavior_AlongLine( _self._delegate, height_ref, ## Ray(ORIGIN, DX) # works ## Ray(ORIGIN, DZ) # works, but only if you trackball it (as expected)... ## Ray(ORIGIN, direction) # fails -- Ray is an ordinary class, not an expr! ###FIX call_Expr(Ray, ORIGIN, direction), # this workaround fixes it for now. # (in prior commit it didn't fix it, but only because of a typo in the testexpr defs # in tests.py, which meant I passed DZ when I thought I passed DX.) range = range )) ### NOTE: drag_handler is also being used to compute the translation from the height, even between drags. delegate = Overlay( Highlightable( Translate( Image("blueflake.png"), ###e needs an option to be visible from both sides (default True, probably) drag_handler._translation ###k ok?? only if that thing hangs around even in between drags, i guess! #e #k not sure if this code-commoning is good, but it's tempting. hmm. ), sbar_text = sbar_text, behavior = drag_handler ), Translate(Rect(2), direction * -0.01), Line(ORIGIN, ORIGIN + direction * height_ref.value, white) ) pass
class _color_toggler(DelegatingInstanceOrExpr): ###WRONGnesses: # - Q: shouldn't we be toggling a flag or enum or int, and separately mapping that to a color to show? # A: Yes, unless we're for general raw color images -- but as a StateArray test this doesn't matter. # args color_ref = Arg(StateRef, doc="stateref to a color variable" ) ###e can we tell StateRef what the value type should be? # appearance/behavior delegate = Boxed( Highlightable( Rect(1, 1, color_ref.value), on_press=Set(color_ref.value, call_Expr(_self.toggle_color, color_ref.value)), sbar_text= "click to change color" #e give index in StateArray, let that be an arg? (if so, is it a fancy stateref option?) ), pixelgap= 0, #e rename -- but to what? bordergap? just gap? (change all gap options to being in pixels?) borderwidth= 1, ###BUG: some of these border edges are 2 pixels, depending on pixel alignment with actual screen bordercolor=black) def toggle_color(self, color): r, g, b = color return ( b, r, g ) # this permits toggling through the cyclic color sequence red, green, blue in that order #e or we could rotate that color-cube on the same diagonal axis but less than 1/3 turn, # to get more colors, if we didn't mind renormalizing them etc... pass
class ChoiceButton(InstanceMacro): """ChoiceButton(choiceval, choiceref, content, background, background_off) [most args optional] displays and permits control of a choice variable stored externally in choiceref, looking like Overlay(background, content) or Overlay(background_off, content) when its own choice value is chosen or unchosen (ie equal or unequal to the stored one), respectively. Most args are optional with useful defaults, or can be given as simpler convenience types (eg colors or text); all args but choiceval can be given as named options, which is useful for customization. (Example: it's useful to put several of these with the same choiceref but different choicevals into a Column. This can be done by mapping one customized variant over a list of choicevals.) The choosing-action occurs on_press of entire thing -- this is not yet changeable (to other kinds of button actions), but should be. #e """ # args choiceval = Arg(Anything) #e declare it as having to be constant per-Instance? Or can it legally vary?? I guess it could; # and I guess it's no error, just weird, for two of these (eg in a column) to share the same choiceval; # in fact, if they're physically separated it's not even weird. sbar_text = Option(str, format_Expr("%s", _self.choiceval)) # mouseover text for statusbar choiceref = ArgOrOption(StateRef) ###k need value-type?? content = ArgOrOption(stubtype, TextRect(format_Expr("%s", _self.choiceval)) ) # Widget2D or something "displayable" in one (eg text or color); defaults to displayed choiceval; # can caller pass a formula in terms of the other options to _self? # Maybe, but not by saying _self! _this(ChoiceButton) == _my? [yes -- _my is now implemented, 061205] background = ArgOrOption(stubtype, Rect(_self.width, _self.height, lightblue) ) # Widget2D, or color (used in Rect a bit larger than content) background_off = ArgOrOption(stubtype, Spacer(_self.width, _self.height)) # ditto, defaults to transparent ##k problem: what we want is to compute an lbox and then use this here in the spacer... or align the content... or .... ##e note that a lot of people find it more convenient to pass around a size, or even pass around a rect, # than to always work with 4 or 6 rect-related attrs... # formulae chosen = eq_Expr( choiceref.value, choiceval) #k ## print "chosen is",chosen ###k assume useful conversions of named options happened already ###e use _value; is it as simple as renaming it delegate and using DelegatingMixin?? Can InstanceMacro do it for us?? # [if we use one of those, be careful not to inherit from Widget2D here, due to its lbox defaults!] _value = Highlightable( Overlay( SpacerFor(Boxed(content)), # kluge to make room around content in _self.width and _self.height, # and make those non-circular; WRONG because won't be properly aligned with backgrounds, # even if content itself would be; # could fix by shifting, but better to figure out how to have better rectlike size options ###e Overlay( ###KLUGE since "Overlay is a stub which only works with exactly two args" If(chosen, background, background_off), content ), ), ## old code: on_press = SetStateRefValue(choiceref, choiceval), # try this code 061211 1113a -- it works, use it: on_press = Set(choiceref.value, choiceval), ##e probably best to say Set(choiceref.value, choiceval), but I think that's nim -- not sure -- # should retest it after Set is revised later today to work with arg1 being lval eg getattr_Expr [061204] sbar_text = sbar_text ) pass # end of class ChoiceButton
class MainCommandToolButton(DelegatingInstanceOrExpr): #e rename? "Toolbutton for one of the main tools like Features, Build, Sketch -- class hierarchy subject to revision" # args toolbar = Arg(Toolbar) # our parent - #e rename parent_toolbar? to distinguish from our flyout_toolbar. toolname = Arg(str) #e.g. "Build" command = Arg(Command, doc = "the command invoked by pressing this toolbutton (might be transient or long lasting)") ###k type ok? subtools = Arg(list_Expr) # list of subtools (for cmenu or flyout), with None as a separator -- or as an ignored missing elt?? # like menu_spec items? # Q: can they contain their own conditions, or just let the list be made using Ifs or filters? # A: subtools can contain their own conditions, for being shown, enabled, etc. they are ui elements, not just operations. #e also one for its toolbar, esp if it's a mutually exclusive pressed choice -- and ways to cause related cmd/propmgr to be entered # state pressed = State(bool, False, doc = "whether this button should appear pressed right now") # formulae plain_bordercolor = If(pressed, gray, white) highlighted_bordercolor = If(pressed, gray, blue) pressed_in_bordercolor = If(pressed, gray, green) # green = going to do something on_release_in pressed_out_bordercolor = If(pressed, gray, white) # white = not going to do anything on_release_out # appearance delegate = Highlightable( plain = Boxed(TextRect(toolname), bordercolor = plain_bordercolor), highlighted = Boxed(TextRect(toolname), bordercolor = highlighted_bordercolor), #e submenu is nim pressed_in = Boxed(TextRect(toolname), bordercolor = pressed_in_bordercolor), pressed_out = Boxed(TextRect(toolname), bordercolor = pressed_out_bordercolor), sbar_text = format_Expr( "%s (click for flyout [nim]; submenu is nim)", toolname ), on_release_in = _self.on_release_in, cmenu_obj = _self ###IMPLEM cmenu_obj option alias or renaming; or call it cmenu_maker?? ) # repr? with self.toolname. Need to recall how best to fit in -- repr_info? ##e # actions def on_release_in(self): if not self.pressed: print "on_release_in %s" % self.toolname self.pressed = True #e for now -- later we might let main toolbar decide if this is ok #e incremental redraw to look pressed right away? or let toolbar decide? self.toolbar._advise_got_pressed(self) else: #### WRONG but not yet another way to unpress: self.pressed = False print "unpressed -- not normal in real life!"### return #e stub def cmenu_spec(self, highlightable): ###IMPLEM this simpler cmenu API (if it still seems good) return map( self.menuitem_for_subtool, self.subtools ) ###e how can that func tell us to leave out one, or incl a sequence? def menuitem_for_subtool(self, subtool): # stub, assume not None etc return ( subtool.name, subtool.cmd_invoke ) pass
def _expr_for_vector_ORIG(self, pointPair): # WARNING: this is not optimized -- it recomputes and discards this expr on every access! # (But it looks like its caller, _expr_instance_for_imagename, caches the expr instances, # so only the uninstantiated expr itself is recomputed each time, so it's probably ok. # [bruce 080323 comment]) arrow = Highlightable( Arrow(color=black, arrowBasePoint=pointPair[0], tailPoint=pointPair[1], tailRadius=0.8, scale=self.glpane.scale), Arrow(color=banana, arrowBasePoint=pointPair[0], tailPoint=pointPair[1], tailRadius=0.8, scale=self.glpane.scale)) return arrow
class checkbox_v3( InstanceMacro ): ##e rename # note: this can do something checkbox_pref can't yet do -- take an external stateref stateref = Arg(StateRef, None) ### default? might not work with a default val yet ### IMPLEM: specify what external state to use, eg a prefs variable, PrefsKey_StateRef(displayOriginAxis_prefs_key) defaultValue = Option(bool, False) ###BUG -- not used! [noticed 061215] ## var = State(bool, defaultValue) var = stateref.value ## # print "var = %r" % (var,) # TypeError: 'module' object is not callable - on line that says on_press = Set(var, not_Expr(var) ) ## # solved: it's probably from above: import Set; from Set import something else but not Set _value = Highlightable( If( var, checkbox_image('mac_checkbox_on.png'), checkbox_image('mac_checkbox_off.png'), ), ## on_press = Set(var, not_Expr(var) ) # wanted (sort of), but doesn't work yet, as explained here: ## AssertionError:... <C_rule_for_formula at 0xff2de10 for 'var' in 'checkbox_v3'> should implement set_for_our_cls # ah, I was assuming "var = stateref.value" would alias var to obj.attr permitting set of obj.attr by set of var, # but of course, that should probably never be permitted to work, since it looks like we're changing self.var instead! # Unless, I either: # - implem a variant of "var = stateref.value" # - decide that since var would normally be unsettable when defined by a formula, that always aliasing it like that # would be ok (often wanted, not too confusing). # PROBLEM WITH PERMITTING IT: you might start out var = State(), and it works to change self.var, # then change var to a formula, and forget you were directly setting it... causing an unwanted actual working set # of other state which you thought you were not touching or even able to touch. # SOLUTION: # - implem a variant of "var = stateref.value, sort of a "state alias" # - have a clear error message from this Set, suggesting to change that assignment to that alias form. # as of 061204 418p: tentatively decided I like that, but the alias variant syntax is not yet designed. # Maybe: ####@@@@ # - var = Alias(stateref.value) ?? [sounds good] # - or just var = State(stateref.value), or is that too confusing? # [yes, too confusing, that arg is for the type, and State is for new state, not an alias... latter is lesser problem] # - or var = StateRef(stateref.value)? no, it's an lval, not a stateref. on_press=Set(stateref.value, not_Expr(var)) # kluge: see if this works (I predict it will) -- workaround of not yet having that alias form of "var = stateref.value" ) pass # end of class checkbox_v3
class TextEditField(DelegatingInstanceOrExpr): text = State( str, "(empty)" ) #e dflt should be an arg, or more to the point, the text should come from a stateref ###DOIT label = Option(str, "(field label)") dialog_title = Option(str, "(title)") dflt_dialog_label = "enter some text; use @@@ for \\n" # a public class attr (for clients to use in longer labels) dialog_label = Option(str, dflt_dialog_label) delegate = Highlightable( SimpleRow( Top(TextRect(label)), Top(TextRect(text)) ), #e long enough? too long? nlines ok? # Top is for when >1 line in text #e highlight form with blue border? on_doubleclick=_self. on_doubleclick, # on_press -> on_doubleclick -- probably better sbar_text="double-click to edit textfield", #e cmenu binding too? ) def on_doubleclick(self): ok, text = grab_text_using_dialog(default=self.text, title=self.dialog_title, label=self.dialog_label) if ok: #e unicode?? (see cad/src MT Node renaming code, or Node.name mmp code, or the Comment Node, # for example code that can handle it) #e filter through a func about what's acceptable? # if it fails, repeat dialog with more specific label? # strip whitespace? etc # eventually customize all that, for now just do what's most useful right here text = text.strip() self.text = text # changes state else: print "cancelled text edit, old text remains: %r" % ( self.text, ) #e some other place to put a message in the UI? return pass
class ToggleShow(InstanceMacro): # args thing = Arg(Widget2D) label = Arg(Widget2D, TextRect("label")) #e or coerce text into a 1-line TextRect -- how do we declare that intent?? if 0: # first make it work with a self-made stateref only, imitating Highlightable, using transient state stateref = Arg(StateRef, Automatic) ###k assumes the dflt can be a way to make one, not a literal one ## Q: how does each stateref we have, e.g. the one here meant for 'open', relate to StatePlace args # we might make or get from a superclass? (like the ones now in Highlightable.py but to be moved to a super of it) # if caller passes one, no need for our own, but to make our own, we'd make it in a StatePlace of our own, I think. ##e 061117 if 0 and 'maybe': ##e might need to also say it's supposed to be boolean # note, on 070115 I revised StateRef (still a stub) but might have broken it due to the arg being passed here (not tested) stateref = Arg(StateRef(bool), Automatic) ##e and also spell out the default location -- assume ipath itself can be coerced into the full stateref stateref = Arg(StateRef(bool), _self.ipath) ####k is ipath local to something like _self, or only rel to the entire model?? his matters if we name the statepath here! stateref = Arg(StateRef(bool), _my_node.open) ###k assumes _my_node.open can be an lval even after _my_node is bound! ####k ##e or we could spell out the default stateref as _self.ipath, assuming that can be coerced into one -- # of course it can't, we also need to say it's boolean (openQ: open is true, closed is false) and with what encoding. # but come to think of it, that is not part of the arg, right? the arg is "some boolean state"... hmm, i guess it is. # but the arg can also be "where to store the state, of whatever kind you want". And we say the type and encoding # if the arg doesn't -- as if the caller can supply partial info in the arg, and we supply defaults rather than # making them get made up by the bare argtype -- which could work by just using a fancified argtype created here. # which the Arg macro could make for us somehow... but that can all wait for later. For now, # we can detect the boolean type by how we try to use the arg, i guess... not sure, maybe just say it right here. # and the default encoding for bools (known to the bool type, not custom to us) is fine. if 1: # works fine, but as of 061126 late, comment out the remaining stmt, since done in InstanceOrExpr superclass rather than here pass ## transient_state = StatePlace('transient') #e move into super along with the ones in Highlightable of which this is a copy #e rename stateref to be specific for open, maybe open_stateref, in that if 0 code above # devel scratch: transient_state is an attr_accessor, which lets you do getattr and setattr. # but what we want it to do for us is tell us an lval for the attr 'open' # so we can retain that, attached to self.open somehow -- as its actual lval, maybe? not sure. # but much like that, since get or set self.open should work that way. # so a stateref (instance of StateRef) is basically an instance which acts as an lval... and can be attached to an attr. # But ExprsMeta right now insists on making its own lval, being passed a formula. Should we kluge-adapt to that or change it? # Guess: better & maybe easier to change it. So we have a new kind of object, not a formula (or a special kind of one), # to use as an rhs and process by ExprsMeta. It's not an lval, that's per-Instance. Is it a formula for making an lval?### # But it might be semantically different, since we don't store the lval as the value for self.open, # but as the value for its lval. hmm.... can we tell ExprsMeta to do this by an assignment to open # of a wrapped formula? it means, get the lval not by making one whose vals come from this formula # but make a property whose lvals come from this formula (which should be per-instance but time-constant, maybe, # tho if not time constant, it might be ok -- not sure ##e). ## open = State('transient','open') ### hmm... maybe 'open' needn't be passed if it gets it like Arg or Option does... maybe the kind is also default something? # so: open = State()? bt say the type and dfault val -- like I did in this: # staterefs.py: 181: LocalState( lambda x = State(int, 1): body(x.value, x.value = 1) ) # # see if this gets the expected asfail: it does! [061121 late] ## set_default_attrs( transient_state, open = True) if 0: # this will be real code someday when no longer nim, but use an easier way first. open = State(bool, True) # default 'kind' of state depends on which layer this object is in, or something else about its class # but for now make it always transient_state # this is a macro like Option # it takes exprs for type & initial val # but those are only evalled in _init_instance or so -- not yet well defined what happens if they time-vary # and note, that form doesn't yet let you point the state into a storage place other than _self.ipath... should it?? # but the importance is, now in python you use self.open for get and set, just as you'd expect for an instance var. else: def get_open(self): #k return self.transient_state.open def set_open(self, val): #k self.transient_state.open = val return open = property(get_open, set_open) pass def _init_instance(self): super(ToggleShow, self)._init_instance() set_default_attrs( self.transient_state, open = True) # constants # Note, + means openable (ie closed), - means closable (ie open) -- this is the Windows convention (I guess; not sure about Linux) # and until now I had them reversed. This is defined in two files and in more than one place in one of them. [bruce 070123] open_icon = Overlay(Rect(0.4), TextRect('-',1,1)) closed_icon = Overlay(Rect(0.4), TextRect('+',1,1)) if 0: open_icon = TextRect('-',1,1) #stub closed_icon = TextRect('+',1,1) #stub else: ####@@@@ I vaguely recall that Highlightable didn't work on text! # and indeed, highlighting doesn't seem to be working on those. # if so, the above'll need revision until that's fixed. # BUT, with these grays anyway, clicks on the text are working. But it might be because the grays are behind them. ###k if 0 and 'varying rect sizes': # how it was during debugging open_icon = Overlay(Rect(0.5,1), TextRect('-',1,1)) # added 0.5 061120 1018p temp debug kluge closed_icon = Overlay(Rect(1,0.5), TextRect('+',1,1)) #061120 changed impicit 1 -> 0.5 else: # easier on the mouse-hand and eye open_icon = Overlay(Rect(0.4), TextRect('-',1,1)) # added 0.5 061120 1018p temp debug kluge closed_icon = Overlay(Rect(0.4), TextRect('+',1,1)) #061120 changed impicit 1 -> 0.5 # _value, and helper formulae ## open = stateref.value # can we make it work to say Set(open, newval) after this?? ####k # the hard part would be: eval arg1, but not quite all the way. we'd need a special eval mode for lvals. # it'd be related to the one for simplify, but different, since for (most) subexprs it'd go all the way. ## openclose = If( open, open_icon, closed_icon ) # Status as of 061121 421p: both if 0 and if 1 cases seem to work fine, provided you restart when changing between them. # (Testing was not extensive, so it might turn out that avoiding other reloads is also needed.) # The known bugfixes that led to this: # - selobj = None in some places (not sure which are needed). # - no usage/change tracking by stateplaces that get set during draw (or that contain glname -- don't know if that matters). # - no usage/change tracking by set_default_attrs. # And other changes that might be helping: # - don't recycle glnames. # - some others I forget, which are marked by 061120 (or maybe 061121) # in comments or stringlits (in this or other files). # - don't track_use on exception in Lval get_value (BUT, i suspect it's actually wrong not to; see cmt there 061121) # - more conservative selobj_still_ok # - mode.update_selobj(event) in leftClick and ReleasedOn # - self.inval(mode) #k needed? (done in two places per method, guess is neither is needed) # # Soon, the needed or not of the above workarounds should be sorted out, # and most of the following debugging-log commentary should be removed. #e if 1: openclose = Highlightable( If_kluge( open, open_icon, closed_icon ), on_press = _self.toggle_open, sbar_text = _this(Highlightable).ipath # this line just for debugging ) ##e we should optim Highlightable's gl_update eagerness # for when some of its states look the same as others! # (no gl_update needed then, at least not just for that change -- # note, this is a special case of the inval optim for when something was changed to an equal value) #e someday be able to say: # on_press = Set( open, not_Expr(open) ) ## silly: on_press = lambda open = open: open = not open # no, open = not open can't work # in fact, you can't use "lambda open = open", since open has to be replaced by ExprsMeta ##k can on_press be an expr to eval, instead of a (constant expr for a) func to call?? ####k ## on_press = call_Expr( lambda xxx: self.open = not self.open, xxx) # no, no assignment in lambda pass else: # so try this form 155p - bug of not working is gone, but now, it always draws the + form! Is it drawing the old one # due to same glname? no (I guess), they should have different names! # is it drawing old one due to not realizing that one is obs? ######where i am # is it failing to invalidate the drawing effect of this instance? (after all, who is it that sees the usage of open? # it must be glpane as if we were using a prefs variable here! is that sufficient?? does it work ok re selobj system???###) # IS THE CHOICE OF DELEGATE being invalled? I think so, since I see the alignment calcs get updated, # or is that just the live gltranslate inside the draw method? # HEY, when I covered up the glpane with this app, then uncovered it, suddenly I see the new selobj, # then the conjunction of both! how can that be? thes are similar to whgat I sawe earlier. conclusion: weird update bugs # in selobj/highlight system, i guess. (maybe it does the main draw and highlight draw on different objects? # try altering color, or using 4 images not 2. ###) # now it sems that mouse around on the closed that looks like open is what makes it look like clsed, or like both. # yes, repeatable, for either change of state. Ok, try that in 'if 1' case of this. ### siilar but not identical # and that time is again does get stuck into the closed state, with the grayrect no longer optiming redraws re stencil buffer. # .. reviewing code in Highlightable, I see some things to try -- see its 061120 comments. # ... I did all that, and the 'gray becomes inactive bug' in 'if 1 openclose case' is still there. howbout the if 0 case? # [later: i think that still had some bad bugs too, not much changed.] # for more, see string lits containing 061120, and for a log see big cmt just below here. openclose = If_kluge( open, ## Highlightable(open_icon, on_press = _self.toggle_open, sbar_text = _self.ipath), ## Highlightable(closed_icon, on_press = _self.toggle_open, sbar_text = _self.ipath), ###BUG - same ipaths? NO, I USED _self BUT MEANT _this(Highlightable)!!! AAArgh! ##k works now?? yes Highlightable(open_icon, on_press = _self.toggle_open, sbar_text = _this(Highlightable).ipath), Highlightable(closed_icon, on_press = _self.toggle_open, sbar_text = _this(Highlightable).ipath), ) pass def toggle_open(self): if 'yet another shot in the dark 061120 1001p': self.env.glpane.selobj = None ##### THIS SEEMS TO FIX THE BUG, at least together with other potshots and if 0 openclose. # theory: old selobjs are being drawn highlighted even when not drawn normally. they mask the real stuff. # but with if 1 openclose it doesn't fix the different bug (gets wedged into closed state). why not??? # this is with no recycling of names, and also selobj=None in recycler. # guess: in if 1, the same glname is used... but it's same literal selobj too, right? and remaking is turned off. # I don't have a theory for 'if 1' bug cause, or for why it only affects the inner thing, not outer one. # Does it only affect that after it's once been hidden? ### if so, is it "you were selobj and then not drawn" that makes it # happen? that might fit the data but I don't see how that could work. # So I added a 2nd 0.5 so neither gray rect form covers the other. but first inner close hits the bug of making it # inactive and act non-highlighted. so i wondered if the glname really only covers the openclose? code says so. # I added a try/except to be sure the PopName occurs; not triggered during this if 1 bug. # # Stopping for night 061120 1027p, summary: I understand some bug causes but not all; wish I could see inside the glselect # reasoning, so plan to add debug prints to glpane. Need to think thru its assumptions re chaotic glname/selobj/size/ # whether-drawn situation, see which are wrong, which might cause the bug. Also - can event processing occur during # paintGL? I hope not, but verify. Also maybe GLPane needs to track frame numbers for selobjs being drawn, # stencil bits being made, vs selobj state mods as tracked by inval.... # # update 061121 953a, where I am: after basic fixes elsewhere [some stateplaces not tracked, some usage tracking disallowed], # and still using all kluge/workarounds from 061120, bug seems totally fixed for if 0 case, all or most for if 1 ##k. # IIRC it was slightly remaining before some usage tracking disallowed, but that is not complaining, which is suspicious. # Anyway, if if 1 works too, plan is to gradually remove the kluges and clean up and keep it working. # BUT it looks like if 1 (using reloaded code) has a bug of some disallowed usage... details to follow. # BUT after restart I don't see it. BTW I recently reenabled reloading -- could that have fixed some bugs (how???), # or could it be that they now occur after reloading but not before it?? indeed, this is after changing if 1->0 and reload, # and looks like it might relate to old state being there, and ###### WHAT IF RELOADED CODE USES THE SAME STATE DIFFERENTLY AND HAS A BUG? ##### # [but, that bug aside, there is still a real problem with whatever usage tracking this # set_default_attrs is doing. [fixed now]] if 0: #061121 822p i've been using if 1 forever, let's see if if 0 works here: it does! either is ok, given the open property. old = self.transient_state.open self.transient_state.open = new = not old ## print "toggle_open changed self.transient_state.open from %r to %r" % (old, new,) else: old = self.open self.open = new = not old ## print "toggle_open changed self.open from %r to %r" % (old, new,) # [obs cmt re open property, but was it talking about that or a partly working State decl? as of 061121 I can't remember:] # should work but doesn't, see bug in notesfile, it delegates self.open eval to _value: 061118 late # (or is it just because the val was not initialized? GUESS, YES ###k) ## self.open = not self.open ### can this work??? it will call set of self.open -- what does the descriptor do for that? # (asfail, or not have __set__ at all?? FIND OUT. the notesfile says the same thing but for a different Q, what was it?) ## WE SHOULD MAKE THIS WORK even if we also make on_press = Set( open, not_Expr(open) ) work, since it's so natural. ### BTW what is it that will notice the inval, and the usage of this when we drew, and know that gl_update is needed? # the same thing that would know a display list content was invalid -- but where is it in our current code (if anywhere)? # I vaguely recall a discussion of that issue, in the displist chunk code or notesfile, weeks ago. # some way to have lvals representing displist contents or frame buffer contents, whose inval means an update is needed. printnim("toggle_open might do a setattr which is not legal yet, and (once that's fixed) might not properly gl_update yet") return _value = SimpleRow( openclose, SimpleColumn( label, If_kluge( open, thing, TextRect("<closed>") #####BUG: I wanted None here, but it exposes a logic bug, # not trivial to fix, discuss in If or Column [see also a discussion in demo_MT.py, 070302]; ##e Spacer(0) can also be tried here [##e should what to show here be an arg??] ) ) ) ## if 0: # if 0 for now, since this happens, as semiexpected: ## ## AssertionError: compute method asked for on non-Instance <SimpleRow#3566(a) at 0xe708cb0> ## ## ##e do we want to make the height always act as if it's open? I think not... but having a public open_height attr ## # (and another one for closed_height) might be useful for some callers (e.g. to draw a fixed-sized box that can hold either state). ## # Would the following defns work:? ## ## # (They might not work if SimpleRow(...).attr fails to create a getattr_Expr! I suspect it doesn't. ####k ) ## ## # [WARNING: too inefficient even if they work, due to extra instance of thing -- see comment for a fix] ## open_height = SimpleRow( ## open_icon, ## SimpleColumn( ## label, ## thing ## )).height ##k if this works, it must mean the SimpleRow gets instantiated, or (unlikely) ## # can report its height even without that. As of 061116 I think it *will* get instantiated from this defn, ## # but I was recently doubting whether it *should* (see recent discussions of TestIterator etc). ## # If it won't, I think wrapping it with Instance() should solve the problem (assuming height is deterministic). ## # If height is not deterministic, the soln is to make open_instance and closed_instance (sharing instances ## # of label), then display one of them, report height of both. (More efficient, too -- only one instance of thing.) ## # (Will the shared instance of label have an ipath taken from one of its uses, or something else? ## # Guess: from the code that creates it separately.) ## ## closed_height = SimpleRow( ## closed_icon, ## SimpleColumn( # this entire subexpr is probably equivalent to label, but using this form makes it more clearly correct ## label, ## None ## )).height pass # end of class ToggleShow
class DraggableHandle_AlongCircle(DelegatingInstanceOrExpr): """ A kind of draggable handle which can be dragged along a line to change the value of a single floating point parameter (representing position along the line, using a scale and origin determined by our arguments). ### TODO: add epydoc parameters to this docstring? not sure that will work for a class! Maybe we need to synthesize those (from the doc options below) into a fake __init__ method for its sake?? """ # == args and options # options for appearance of handle appearance = Option( Drawable, Center(Rect(0.2, 0.2, white)), doc = "appearance, when not highlighted") appearance_highlighted = Option( Drawable, appearance, doc = "appearance, when highlighted") sbar_text = Option(str, "draggable handle along circle", doc = "statusbar text on mouseover") # state variable controlled by dragging rotationDistanceRef = Option(StateRef, doc = "stateref to a dragged disctance variable") # action options, for Highlightable to do after the ones that come from # DragBehavior_AlongLine [new feature of this and Highlightable, 080129] on_press = Option(Action) on_drag = Option(Action) on_release = Option(Action) on_release_in = Option(Action, on_release) on_release_out = Option(Action, on_release) on_doubleclick = Option(Action) #origin of the handle itself origin = Option( Point, ORIGIN) # Center of the circle whose perimeter serves as a path along which to # drag center = Option( Point, ORIGIN) #Axis of the circle. axis = Option( Vector, DX, doc = "vector giving direction and scale") #radius Vector radiusVector = Option( Vector, DX, doc = "vector giving direction and scale") #angle range for the rotation range_for_rotation = Option(tuple_Expr, None, doc = "range limit of angle (tuple)") # == internal instances and formulae (modified from test_statearray_3.py) _drag_handler = Instance( DragBehavior_AlongCircle( _self._delegate, rotationDistanceRef, origin, center, axis, radiusVector, range_for_rotation = range_for_rotation)) #QUESTION: Should the 'RotateTranslate' transform from exprs.transforms be #used here? -- Ninad 2008-02-13 delegate = \ Highlightable( Translate( appearance, _drag_handler._rotation #k ok?? only if that thing hangs around even in between drags, i guess! #e #k not sure if this code-commoning is good, but it's tempting. hmm. ), highlighted = Translate( appearance_highlighted, _drag_handler._rotation ), sbar_text = sbar_text, behavior = _drag_handler, on_press = _self.on_press, on_drag = _self.on_drag, # (note: no need to pass on_release) on_release_in = _self.on_release_in, on_release_out = _self.on_release_out, on_doubleclick = _self.on_doubleclick, ) pass # end of class
class DraggablyBoxed( Boxed ): # 070316; works 070317 [testexpr_36] before ww,hh State or resizable, and again (_36b) after them # inherit args, options, formulae from Boxed thing = _self.thing ###k WONT WORK unless we kluge ExprsMeta to remove this assignment from the namespace -- which we did. ###e not sure this is best syntax though. attr = _super.attr implies it'd work inside larger formulae, but it can't; # attr = Boxed.attr might be ok, whether it can work is not reviewed; it too might imply what _super does, falsely I think. extra1 = _self.extra1 borderthickness = _self.borderthickness rectframe = _self.rectframe # a pure expr # new options resizable = Option(bool, False, doc="whether to make it resizable at lower right") # works 070317 10pm (testexpr_36b) except for a few ###BUGS [updated info 070318 7pm]: # + [fixed] the wrong corner resizes (top right) (logic bug) # + [fixed] resizer doesn't move (understood -- wrong expr for its posn; commented below) # - negative sizes allowed (missing feature - limit the drag - need new DragBehavior feature) # - no clipping to interior of rectframe (missing feature - draw something clipped) # - perspective view ought to work, but entirely ###UNTESTED. # also, cosmetic bugs: # - resizer doesn't follow mouse in rotated coordsys, even in ortho view (though it's still useable). # (This is not surprising -- we're using the wrong kind of DragBehavior as a simple kluge.) # - the resizer is ugly, in shape & color. clipped = Option( bool, False, doc="###doc") #070322 new feature ### make True default after testing? # state # WARNING: due to ipath persistence, if you revise dflt_expr you apparently need to restart ne1 to see the change. ## ww = State(Width, thing.width + 2 * extra1) # replaces non-state formula in superclass -- seems to work ## hh = State(Width, thing.height + 2 * extra1) ## # now we just need a way to get a stateref to, effectively, the 3-tuple (ww,hh,set-value-discarder) ... instead, use whj: whj = State(Vector, V_expr(thing.width + 2 * extra1, -thing.height - 2 * extra1, 0)) #e not sure this is sound in rotated coordsys translation = State(Vector, ORIGIN) # override super formulae ww = whj[0] # seems to work hh = neg_Expr( whj[1] ) # negative is needed since drag down (negative Y direction) needs to increase height # (guess: neg_Expr wouldn't be needed if we used an appropriate new DragBehavior in resizer, # rather than our current klugy use of SimpleDragBehavior) # appearance rectframe_h = Instance( Highlightable( ## rectframe(bordercolor=green),####### cust is just to see if it works -- it doesn't, i guess i sort of know why ##bug: __call__ of <getattr_Expr#8243: (S._self, <constant_Expr#8242: 'rectframe'>)> with: () {'bordercolor': (0.0, 1.0, 0.0)} ##AssertionError: getattr exprs are not callable TopLeft(rectframe), #e different colored hover-highlighted version?? for now, just use sbar_text to know you're there. sbar_text= "draggable box frame", # this disappears on press -- is that intended? ###k behavior=SimpleDragBehavior( # arg1: the highlightable _self.rectframe_h, # arg2: a write-capable reference to _self.translation ## fails - evalled at compile time, not an expr: LvalueFromObjAndAttr( _self, 'translation'), ###BUG: why didn't anything complain when that bug caused the state value to be an add_Expr, not a number-array? call_Expr(LvalueFromObjAndAttr, _self, 'translation'), #e alternate forms for this that we might want to make work: # - getattr_StateRef(_self, 'translation') # simple def of the above # - StateRef_for( _self.translation ) # turns any lvalue into a stateref! Name is not good enough, though. ))) resizer = Instance( Highlightable( Center(Rect(extra1, extra1)), #e also try BottomRight highlighted=Center(Rect(extra1, extra1, white)), pressed=_my.highlighted, sbar_text="resize the box frame", behavior=SimpleDragBehavior( _self.resizer, call_Expr(LvalueFromObjAndAttr, _self, 'whj')))) ###BUG: in Boxed, rectframe comes first, so lbox attrs are delegated to it. We should do that too -- # but right now we draw it later in order to obscure the thing if they overlap. With clipping we won't need that -- # but without clipping we will. If the latter still matters, we need a version of Overlay with delegation != drawing order, # or, to delegate appearance and layout to different instances ourselves. (Or just to define new formulae for lbox -- easiest.) #e drawme = Instance( Overlay( If( clipped, Clipped( thing, planes=[ call_Expr(clip_to_right_of_x0, -thing.bleft - extra1 + ww - borderthickness), # note: the (- borderthickness) term makes the clipping reach exactly # to the inner rectframe edge. Without it, clipping would reach to the outer edge, # which for 3d objects inside the frame can cause them to obscure it. # (Other interesting values are (- extra1) and (- borderthickness/2.0), # but they both look worse, IMHO.) call_Expr(clip_below_y0, thing.btop + extra1 - hh + borderthickness) ]), thing, ), Translate(rectframe_h, V_expr(-thing.bleft - extra1, thing.btop + extra1)), If( resizable, ## Translate( resizer, V_expr( thing.bright + extra1, - thing.bbottom - extra1)) ###WRONG - this posn is fixed by thing's lbox dims, not affected by ww, hh; # will the fix be clearer if we use a TopLeft alignment expr? # It'd be hard to use it while maintaining thing's origin for use by external alignment -- # but maybe there's no point in doing that. Translate( resizer, V_expr(-thing.bleft - extra1 + ww, thing.btop + extra1 - hh))))) _value = Translate( drawme, ## DisplayListChunk( drawme), ###k this DisplayListChunk might break the Highlightable in rectframe_h ##### translation) pass # end of class DraggablyBoxed
class DraggableHandle_AlongLine(DelegatingInstanceOrExpr): ### TODO: all options might need renaming! replace "height" everywhere. """ A kind of draggable handle which can be dragged along a line to change the value of a single floating point parameter (representing position along the line, using a scale and origin determined by our arguments). ### TODO: add epydoc parameters to this docstring? not sure that will work for a class! Maybe we need to synthesize those (from the doc options below) into a fake __init__ method for its sake?? """ # == args and options # options for appearance of handle appearance = Option( Drawable, Center(Rect(0.2, 0.2, white)), doc = "our appearance, when not highlighted") ###e take other args of Highlightable? appearance_highlighted = Option( Drawable, appearance, doc = "our appearance, when highlighted") sbar_text = Option(str, "draggable handle", doc = "our statusbar text on mouseover") # state variable controlled by dragging height_ref = Option(StateRef, doc = "stateref to a height variable") # TODO: rename, redoc range = Option(tuple_Expr, None, doc = "range limit of height (tuple)") ### MAYBE: RENAME to avoid conflict with python range in this code ### TODO: take values from the stateref if this option is not provided # action options, for Highlightable to do after the ones that come from # DragBehavior_AlongLine [new feature of this and Highlightable, 080129] on_press = Option(Action) on_drag = Option(Action) on_release = Option(Action) on_release_in = Option(Action, on_release) on_release_out = Option(Action, on_release) on_doubleclick = Option(Action) # line along which to drag it, and how to interpret the state as a position along that line (origin and scale) origin = Option( Point, ORIGIN) direction = Option( Vector, DX, doc = "vector giving direction and scale") # providing a default value is mainly just for testing #If this is false, the 'highlightable' object i.e. this handle #won't be drawn. The delegate (that defines a Highlightable) #We define an If_expr to check whether to draw the highlightable object. #[by Ninad] # [this should probably be revised, with hasValidParamsForDrawing replaced # with an overridable compute method, for robustness -- current implem # is suspected of causing tracebacks from insufficient update of this # state. Need to review whether methodname needs to be hasValidParamsForDrawing # to conform with any API. -- bruce 080409 comment] should_draw = State(bool, True) # == internal instances and formulae (modified from test_statearray_3.py) _drag_handler = Instance( DragBehavior_AlongLine( _self._delegate, height_ref, call_Expr(Ray, origin, direction), # note: we need call_Expr since Ray is an ordinary class, not an expr! ###FIX range = range )) # note: _drag_handler is also being used to compute the translation from the height, even between drags. #@see: DnaStrand_ResizeHandle.hasValidParamsForDrawing #@see: definition of State attr should_draw delegate = \ If_expr( _self.should_draw, Highlightable( Translate( appearance, _drag_handler._translation #k ok?? only if that thing hangs around even in between drags, i guess! #e #k not sure if this code-commoning is good, but it's tempting. hmm. ), highlighted = Translate( appearance_highlighted, _drag_handler._translation ), sbar_text = sbar_text, behavior = _drag_handler, on_press = _self.on_press, on_drag = _self.on_drag, # (note: no need to pass on_release) on_release_in = _self.on_release_in, on_release_out = _self.on_release_out, on_doubleclick = _self.on_doubleclick ) #end of Highlightable ) #end of If_expr def hasValidParamsForDrawing(self): """ Overridden in subclasses. Default implementation returns True if this object (the highlightable) can be drawn without any known issues @see: DnaStrand_ResizeHandle.hasValidParamsForDrawing for more notes. """ self.should_draw = True return self.should_draw pass # end of class
class DraggableObject(DelegatingInstanceOrExpr): """DraggableObject(obj) is a wrapper which makes any model object draggable (###doc the details), and also helps provides a context menu specific to obj. [##e It may be extended to make obj click-selectable or even region-selectable, at the proper times, too.] WARNING: Experimental -- API/organization will surely change, integrating not only rotation, but click to select, etc. The resulting wrapper will typically be applied by model->view macros. In fact, it's more complicated than that: the selection-click controller will wrap single objects, but the draggability wrapper is more likely to be organized something like this, where the named localvars refer to sets whose membership depends on selection: visibles = DisplayListChunk(fixed_stuff) + distortedly_moving_stuff + DraggableObject(DisplayListChunk(dragging_as_a_unit_stuff)). The distortedly_moving_stuff includes things like external bonds between fixed and being-dragged atoms, which have to stretch in individual ways during the drag. """ # args obj = Arg(ModelObject) # options #e selectable = Option(bool, True, doc = "whether to let this object be click-selectable in the standard way") [see selected] rotatable = Option( bool, True, doc= "whether to let this object rotate about its center using MMB/Alt/Option drags" ) # This is intended to implement an initial subset of the "New motion UI" [070225 new feature] # [###e default will change to False after testing] # WARNING: as an optim, we might require that this be True initially, or even always (i.e. be a constant), # if it will ever be True during the Instance's lifetime -- not sure. If so, this requirement must at least be documented, # and preferably error-detected. ###FIX (if we do require that) # experimental kluge 070314 _kluge_drag_handler = Option( Anything, _self, doc= "object to receive our on_press/on_drag/on_release events, in place of _self" ) # state selected = State(bool, False) ###KLUGE test stub, only set when debug070209 translation = Option( Vector, V(0, 0, 0), #070404 doc= "initial translation [WARNING: might merge with misnamed self.motion (a State attr) to make a StateOption]" ) motion = State( Vector, _self.translation ) # publicly visible and settable (but only with =, not +=). ##e rename to translation? (by making it a StateOption) ##e (or deprecate the concept of StateOption but make any State settable initially by a special option not just with same name? ## eg either initial_attr or initial_data = [something with dict or attr access to the data] ??) ##e NOTE [070404]: I miscoded translation as Arg rather than Option, and said StateArg rather than StateOption in docstring, # though intending only named uses of it -- is this evidence that Arg / Option / Parameter should be the same, # that Option should be the default meaning, and positional arglists should be handled differently and as an extra thing # (eg like the old _args feature -- which leads to clearer code when subclassing)?? Guess: quite possibly, but needs more thought. # WARNING: use of += has two distinct bugs, neither error directly detectable: # - changes due to += (or the like) would not be change tracked. # (But all changes to this need to be tracked, so our drawing effects are invalidated when it changes.) # - value might be a shared Numeric array -- right now use of = to set this doesn't copy the array to make us own it. rotation = State(Quat, Q(1, 0, 0, 0)) #070225 new feature -- applied around object center # experiment 070312: works (see test_StateArrayRefs_2) ###doc ##e clean up ##k is it making the usual case slow in a significant way?? delta_stateref = Option(StateRef, call_Expr(LvalueFromObjAndAttr, _self, 'motion'), doc="#doc") use_motion = delta_stateref.value # geometric attrs should delegate to obj, but be translated by motion as appropriate. ##e Someday we need to say that in two ways: # - the attrs in the "geometric object interface" delegate as a group (rather than listing each one of them here) # - but when they do, they get passed through a change-of-coords boundary, and they know their own coordsystems, # so the right thing happens. # But for now we have no way to say either thing, so we'll add specific formulas for specific attrs as needed. [070208] ##e Note that before the obj types know how to translate due to type, the interface (which knows the attrs indivly) # could know it. So, delegation of all attrs in an interface can be done by special glue code which also knows # how to transform them in useful ways, by knowing about those attrs and what transforms are useful. # This is useful enough to keep, even once its default transforms can come from declared attr types & # values knowing their coordsys. It adds value to that since interfaces can always know special cases about specific attrs. if 0: # update 070209 late: try doing this in Translate below, with the other involved exprs delegating as usual... ####k center = obj.center + motion # following comments are from when the above was 'if 1' a day or two ago -- still relevant since general [##e refile??]: # Problem: won't work for objs with no center! Solution for now: don't try to eval the self attr then. # Not perfect, since what ought to be AttributeError will turn into some other exception. ##e One better solution would involve declared interfaces for obj, and delegation of all attrs in interfaces # of a certain kind (geometric), so if obj met more interfaces and had more attrs, those would be included, # but if not, we would not have them either. ##e Or alternatively, we could provide an easy way to modify the above formula # to specify a condition under which center should seem to exist here, with that cond being whether it exists on obj. ### A potential problem with both solutions: misleasing AttributeError messages, referring to self rather than obj, # would hurt debugging. So we probably want to reraise the original AttributeError in cases like that, whatever # the way in which we ask for that behavior. That means one construct for "passing along attr missingness", # but *not* a composition of one construct for saying when this attr is there, and one for asking whether another is. # Note: can't we delegate center (& other geometry) through the display delegate below, if Highlightable passes it through # and Translate does the coordinate transformation? ###e # appearance obj_name = call_Expr(node_name, obj) #070216 # Note: node_name is used in MT_try2; it's better than using _e_model_type_you_make (for our use in sbar_text, below). # BTW, node_name is a helper function associated with ModelTreeNodeInterface (informal so far). # # If you want to wrap an object with extra info which specifies its node_name, use ... what? ###k hmm, I forget if there # is a way partway through being implemented... # maybe WithAttributes( Center(Rect(0.4, 0.4, green)), mt_name = "green rect #n" )... i guess yes, ### TRY IT # should clean this situation up, use Adaptor term for that pattern # of interface conversion, etc... [070404 updated comment] # (Note [070216]: I had a bug when I had a comma after the above def. This made obj_name, included directly in another expr, # turn into a singleton tuple of the call_Expr value, but when included as _self.obj_name (normally equivalent to obj_name), # turn into something else (since eval of a tuple must not descend inside it -- guess, might have been a tuple_Expr). # I'm not sure how to detect this error except to stop permitting tuple(expr) to be allowed as abbrev for a tuple_Expr -- # which seems too inconvenient -- or to figure out a way for the formula scanner to detect it (and make it illegal as the # rhs of an assignment into a class namespace -- probably ok to make illegal). ##DOIT sometime) obj_drawn = If( selected, Overlay(obj, Rect( 1, 1, blue)), ##### WRONG LOOK for selected, but should work [070209] #BUG: Rect(1,lightblue) is gray, not light blue -- oh, it's that failure to use type to guess which arg it is! obj) sbar_text_for_maybe_selected = If(selected, " (selected)", "") delegate = Highlightable( # Note 070317: since Highlightable is outside of RotateTranslate, its coordsys doesn't change during a drag, # thus avoiding, here in DraggableObject, the bug that came up in the first implem of DraggablyBoxed, # whose highlightable rectframe was moving during the drag, but was also being used to supply the coordsys # for the drag events. This bug is actually in SimpleDragBehavior above, and the fix will be confined to that class. # # plain appearance RotateTranslate(obj_drawn, rotation, use_motion), # hover-highlighted appearance (also used when dragging, below) highlighted=RotateTranslate( DisplayListChunk( # This inner DisplayListChunk, in theory, might help make up for current implem of disabling them inside WarpColors... # in my tests, it didn't make a noticeable difference (probably since obj is fast to draw). [070216 2pm] # # Note: if obj has its own DisplayListChunk, does that notice the value of whatever dynenv var is altered by WarpColors?? # We'll have to make it do so somehow -- perhaps by altering the displist name by that, or turning off displists due to it. # For this initial implem [070215 4pm], we did the latter. ## WarpColors( obj_drawn, lambda color: ave_colors( 0.3, white, color ) ), # whiten the color -- ugly ## WarpColors( obj_drawn, lambda color: yellow ), # "ignore color, use yellow" -- even uglier ## WarpColors( obj_drawn, lambda color: ave_colors( 0.2, white, color ) ), # whiten, but not as much -- less ugly WarpColors( obj_drawn, lambda color: ave_colors(0.1, white, color) ), # whiten, even less -- even less ugly [best so far] ## WarpColors( obj_drawn, lambda color: ave_colors( 0.2, gray, color ) ), # gray-end instead of whiten -- not quite as good ## WarpColors( obj_drawn, lambda color: (color[1],color[2],color[0]) ), # permute the hues... ), rotation, use_motion), pressed=_my.highlighted, # pressed_in and pressed_out appearance ###BUG (when we gave pressed_in and pressed_out separately -- ###UNTESTED since then): # this pressed_out appearance seems to work for DNA cyls but not for draggable PalletteWell items! [070215 4pm] ## sbar_text = format_Expr( "Draggable %r", obj ), ##e should use %s on obj.name or obj.name_for_sbar, and add those attrs to ModelObject interface # (they would delegate through viewing wrappers on obj, if any, and get to the MT-visible name of the model object itself) ##e [Can we implem something like try_Expr( try1, try2, try3) which evals to the first one evalling without an exception?? # But that doesn't seem safe unless you have to list the permissible exceptions (like in Python try/except). # The use of this here (temporary) would be to look for obj.name, then try a different format_Expr if that fails. # getattr(obj, 'name', dflt) would get us by, but would not as easily permit alternate format_Exprs in the two cases.] ## # older highlighted or pressed_in appearance (not sure which it was before I inserted the args above this) -- zapping it 070216 late ## If( eval_Expr(constant_Expr(constant_Expr(debug070209))), ## ###e need option or variant of If to turn off warning that cond is a constant: warn_if_constant = False?? ## # also make the printed warning give a clue who we are -- even if we have to pass an option with the text of the clue?? ## Translate( Boxed(obj), motion), ## #######070209 TEST THIS KLUGE -- note it does not include selected appearance ## # (but HL might incl it anyway? sometimes yes sometimes no, not sure why that would be -- ah, it depends on whether ## # mouse is over the moved object (which is silly but i recall it as happening in regular ne1 too -- ###BUG) ## #e not good highlight form ## ####BUG: the layout attrs (lbox attrs, eg bleft) are apparently not delegated, so the box is small and mostly obscured ## Translate( obj, motion) ## ), ## _obj_name = call_Expr(node_name, obj), #070216 # that can't work yet -- it tries to define a new attr in an object (this Highlightable) from outside, # accessible in other option formulae as _this(Highlightable)._obj_name... # instead, I moved this def into _self (far above) for now. sbar_text=format_Expr("%s%s (can be dragged)", obj_name, sbar_text_for_maybe_selected), # revised 070216 # This adds some info to sbar_text about what we can do with obj (drag, select, etc)... #e someday, maybe the dynenv wants to control how info of several kinds turns into actual sbar_text. ## on_press = _self.on_press, ## on_drag = _self.on_drag, ## on_release = _self.on_release, on_press=_kluge_drag_handler.on_press, on_drag=_kluge_drag_handler.on_drag, on_release=_kluge_drag_handler.on_release, cmenu_maker= obj ###e 070204 experimental, API very likely to be revised; makes Highlightable look for obj.make_selobj_cmenu_items ) ### DESIGN Q: do we also include the actual event binding (on_press and on_drag) -- for now, we do -- # or just supply the Draggable interface for moving self.obj # and let the caller supply the binding to our internal "cmd" drag_from_to?? ###e # has Draggable interface (see demo_polygon.py for explan) for changing self.motion def _cmd_drag_from_to( self, p1, p2): #e rename drag_hitpoint_from_to? (in the Draggable Interface) """[part of the Draggable Interface; but this interface is not general enough if it only has this method -- some objects need more info eg a moving mouseray, screenrect, etc. Either this gets passed more info (eg a dragevent obj), or we keep the kluge of separate self dynenv queries (like for mousepoint and screenrect), or we provide glue code to look for this method but use more general ones if it's not there. ###e BTW, that interface is a myth at present; all actual dragging so far is done using on_press/on_drag/on_release, with this method at best used internally on some objs, like this one. [as of 070313]] """ if self._delegate.altkey: assert 0, "should no longer be called" ## ###KLUGE, just a hack for testing Highlightable.altkey [070224]; later, do rotation instead (per "New motion UI") ## # (Is it also a ###KLUGE to detect altkey within this method, rather than caller detecting it and passing a flag ## # or calling a different method? YES.) ## ## self.motion = self.motion + (p2 - p1) * -1 ## # change self.rotation... by a quat which depends on p2 - p1 projected onto the screen... or the similar mouse x,y delta... ## ###KLUGE: assume DZ is toward screen and scale is standard.... ## # wait, ###BUG, we don't even have enough info to do this right, or not simply, starting from p1, rather than startpoint... ## dx,dy,dz = p2 - p1 ## rotby = Q(p1,p2) ###WRONG but ought to be legal and visible and might even pretend to be a trackball in some cases and ways ## self.rotation = self.rotation + rotby ## # print "%r motion = %r rotation = %r" % (self, self.motion, self.rotation) else: ## self.motion = self.motion + (p2 - p1) self.delta_stateref.value = self.delta_stateref.value + (p2 - p1) return ##e something to start & end the drag? that could include flush if desired... # can push changes into the object def flush(self, newmotion=V(0, 0, 0)): self.delegate.move( self.use_motion + newmotion ) ###k ASSUMES ModelObject always supports move (even if it's a noop) ###IMPLEM # note, nothing wrong with modelobjects usually having one coordsys state which this affects # and storing the rest of their data relative to that, if they want to -- but only some do. ## self.motion = V(0,0,0) self.delta_stateref.value = V(0, 0, 0) # if told to move, flush at the same time def move(self, motion): self.flush(motion) return # on_press etc methods are modified from demo_polygon.py class typical_DragCommand #e note: it may happen that we add an option to pass something other than self to supply these methods. # then these methods would be just the default for when that was not passed # (or we might move them into a helper class, one of which can be made to delegate to self and be the default obj). [070313] def on_press(self): point = self.current_event_mousepoint( ) # the touched point on the visible object (hitpoint) # (this method is defined in the Highlightable which is self.delegate) self.oldpoint = self.startpoint = point # decide type of drag now, so it's clearly constant during drag, and so decision code is only in one place. # (but note that some modkey meanings might require that changes to them during the same drag are detected [nim].) if self._delegate.altkey: self._this_drag = 'free x-y rotate' #e more options later, and/or more flags like this (maybe some should be booleans) ###e or better, set up a function or object which turns later points into their effects... hmm, a DragCommand instance! ##e or should that be renamed DragOperation?? self._screenrect = (ll, lr, ur, ul) = self.screenrect(self.startpoint) # these points should be valid in our delegate's coords == self's coords self._dx = _dx = norm(lr - ll) self._dy = _dy = norm(ur - lr) self._dz = cross( _dx, _dy ) # towards the eye (if view is ortho) (but alg is correct whether or not it is, i think) ###k check cross direction sign self._scale = min(vlen(lr - ll), vlen(ur - lr)) * 0.4 # New motion UI suggests that 40% of that distance means 180 degrees of rotation. # We'll draw an axis whose length is chosen so that dragging on a sphere of that size # would have the same effect. (Maybe.) self._objcenter = self._delegate.center self.startrot = +self.rotation else: self._this_drag = 'free x-y translate' if debug070209: self.ndrags = 0 return def on_drag(self): # Note: we can assume this is a "real drag", since the caller (ultimately a selectMode method in testmode, as of 070209) # is tracking mouse motion and not calling this until it becomes large enough, as the debug070209 prints show. oldpoint = self.oldpoint # was saved by prior on_drag or by on_press point = self.current_event_mousepoint(plane=self.startpoint) if debug070209: self.ndrags += 1 ## if (self.ndrags == 1) or 1: ## print "drag event %d, model distance = %r, pixel dist not computed" % (self.ndrags, vlen(oldpoint - point),) if self._this_drag == 'free x-y rotate': # rotate using New motion UI # [probably works for this specific kind of rotation, one of 4 that UI proposes; # doesn't yet have fancy cursors or during-rotation graphics; add those only after it's a DragCommand] # two implem choices: # 1. know the eye direction and the screen dims in plane of startpoint, in model coords; compute in model coords # 2. get the mouse positions (startpoint and point) and screen dims in window x,y coords, compute rotation in eye coords, # but remember to reorient it to correspond with model if model coords are rotated already. # Not sure which one is better. # In general, placing user into model coords (or more precisely, into object local coords) seems more general -- # for example, what if there were several interacting users, each visible to the others? # We'd want each user's eye & screen to be visible! (Maybe even an image of their face & screen, properly scaled and aligned?) # And we'd want their posns to be used in the computations here, all in model coords. # (Even if zoom had occurred, which means, even the user's *size* is quite variable!) # I need "user in model coords" for other reasons too, so ok, I'll do it that way. # # [Hey, I might as well fix the bug in current_event_mousepoint which fakes the center of view, at the same time. # (I can't remember its details right now, but I think it assumed the local origin was the cov, which is obviously wrong.) # (But I didn't look at that code or fix that bug now.)] vec = point - self.startpoint uvec = norm(vec) #k needed?? axisvec = cross( self._dz, uvec ) # unit length (suitable for glRotate -- but we need to use it to make a quat!) axisvec = norm( axisvec) # just to be sure (or to reduce numerical errors) scale = self._scale draw_axisvec = axisvec * scale #e times some other length constant too? center = self._objcenter self.axisends = (center - axisvec, center + axisvec ) # draw a rotation axis here ###e self.degrees = degrees = vlen( vec ) / scale * 180.0 # draw a textual indicator with degrees (and axisvec angle too) ###e ###e or print that info into sbar? or somewhere fixed in glpane? or in glpane near mouse? # now set self.rotation to a quat made from axisvec and degrees theta = degrees / 360.0 * 2 * pi # print "axisvec %r, degrees %r, theta %r" % (axisvec ,degrees,theta) rot = Q(axisvec, theta) self.rotation = self.startrot + rot # note use of self.startrot rather than self.rotation on rhs # avoid += to make sure it gets changed-tracked -- and since it would be the wrong op! elif self._this_drag == 'free x-y translate': self._cmd_drag_from_to( oldpoint, point) # use Draggable interface cmd on self else: assert 0 self.oldpoint = point return def on_release(self): #e here is where we'd decide if this was really just a "click", and if so, do something like select the object, # if we are generalized to become the wrapper which handles that too. if debug070209: if not self.ndrags: # print "release (no drags)" # ie a click self.selected = not self.selected ###KLUGE test stub else: pass # print "release after %d drags" % self.ndrags self.ndrags = 0 pass pass # end of class DraggableObject
class MakeCrossovers_Handle(DelegatingInstanceOrExpr): should_draw = State(bool, True) radius = 0.8 point1 = Arg(Point) point2 = Arg(Point) scale = Arg(float) crossoverSite_marker = Option( Action, doc='The CrossoverSite Marker class which instantiates this handle') #Command object specified as an 'Option' during instantiation of the class #see DnaSegment_EditCommand class definition. command = Option(Action, doc='The Command which instantiates this handle') crossoverPairs = Option(tuple, ()) #Stateusbar text. Variable needs to be renamed in superclass. sbar_text = Option(str, "Click on the handle to create this crossover", doc="Statusbar text on mouseover") delegate = If_expr( _self.should_draw, Highlightable(Overlay( Cylinder((call_Expr(_self.crossoverPairs[0].posn), call_Expr(_self.crossoverPairs[3].posn)), radius=radius, color=silver), Cylinder((call_Expr(_self.crossoverPairs[1].posn), call_Expr(_self.crossoverPairs[2].posn)), radius=radius, color=silver)), Overlay( Cylinder((call_Expr(_self.crossoverPairs[0].posn), call_Expr(_self.crossoverPairs[3].posn)), radius=radius, color=banana), Cylinder((call_Expr(_self.crossoverPairs[1].posn), call_Expr(_self.crossoverPairs[2].posn)), radius=radius, color=banana)), on_press=_self.on_press, on_release=_self.on_release, sbar_text=sbar_text)) ##delegate = If_expr(_self.should_draw, ##Highlightable(Cylinder((point1, point2), ##radius = radius, ##color = silver), ##Cylinder((point1, point2), ##radius = radius, ##color = banana), ##on_press = _self.on_press, ##on_release = _self.on_release)) ##delegate = \ ##If_expr( ##_self.should_draw, ##Highlightable(Arrow( ##color = silver, ##arrowBasePoint = point1, ####tailPoint = norm(vector)*1.0, ##tailPoint = point2, ##radius = radius, ##scale = scale), ##Arrow( ##color = banana, ##arrowBasePoint = point1, ####tailPoint = norm(vector)*1.0, ##tailPoint = point2, ##radius = radius, ##scale = scale), ##on_press = _self.on_press, ##on_release = _self.on_release ) ) # def hasValidParamsForDrawing(self): """ Overridden in subclasses. Default implementation returns True if this object (the highlightable) can be drawn without any known issues @see: DnaStrand_ResizeHandle.hasValidParamsForDrawing for more notes. """ ##self.should_draw = True return self.should_draw def on_press(self): pass def on_release(self): self.command.makeCrossover(self.crossoverPairs) self.crossoverSite_marker.removeHandle(self)
class PalletteWell(DelegatingInstanceOrExpr): """A place in the UI which can make copies of its expr for dragging to whereever you want [not fully working yet] """ # experimental, really a stub, 070212 -- but sort of works! (as tested in dna_ribbon_view.py) expr = ArgExpr( Anything ) #e not really Anything, but some interface ##e should really be StateArg world = Option( World ) ###e find in env, instead?? maybe not, since in typical cases we might rather make in some parent in # the model anyway, which depends on which kind of obj we are and which pallette we're in... # maybe we even need a make-function to be passed in. # If it was an arg, it'd be natural as arg1 (since this is like a method on the world); # but since it might become a dynamic env var, I'll use an option for now. # If it was a dynamic var by default but could be some container obj, an option would be good for that too (renamed #e). type = Option( str, doc= "type of thing to tell world we're making [type, api subject to change]" ) ###BUG: passing type to world.make_and_add is nim what_to_make_nickname = or_Expr( type, "something" ) #e classname of expr, after burrowing? some other namelike attr of expr? # (note, hard to have that, unless expr is a new special Instance type of "makable" rather than a bare expr -- # and it probably ought to be. ##e) sbar_text = Option(str, format_Expr("make %s", what_to_make_nickname)) _what_to_make = DraggableObject(_self.expr) ##e rename DraggableObject -> Draggable? I misrecalled it as that... and "object" is arguably redundant. _newobj = State( Anything, None) # set internally to an object we create during _self.on_press delegate = Highlightable( Boxed(expr, borderthickness=2 * PIXELS), # plain ###BUG: Boxed fails with exprs that don't have bleft, with a very hard to decipher exception Boxed(expr, borderthickness=2 * PIXELS, bordercolor=blue), # highlight Boxed(expr, borderthickness=2 * PIXELS, bordercolor=green ), # [pressed] green signifies "going" (mainly, green != blue) on_press=_self.on_press, on_drag=_newobj.on_drag, on_release=_newobj.on_release, sbar_text= sbar_text ###e UI DESIGN: need to also pass sbar text (or a func to get it from selobj) for use during the drag ) def on_press(self): # make a new object self._newobj = self.world.make_and_add(self._what_to_make) ##e also pass the type option, taken from a new _self option? ###e UI DESIGN FLAW: we should probably not actually make the object until the drag starts... # better, make something now, but only a fake, cursor-like object (not placed in the model or its tree) # (maybe a thumbnail image made from expr? maybe use PixelGrabber on self, to get it?? #e) # and only make a real model object when the drag *ends* (in a suitable mouse position -- otherwise cancel the make). if 'kluge 070328, revised 070401': ###e see also comments in class World, 070401 self._newobj.copy_saved_coordsys_from(self.world._coordsys_holder) # start a drag of the new object; first figure out where, in world coordinates, and in the depth plane # in which you want the new object to appear (and move the object there -- without that it'll be at the origin) point_in_newobj_coords = self._newobj.current_event_mousepoint( plane=ORIGIN) ### LOGIC BUG: this seems to work, but it presumbly has this bug: in current implem, self._newobj's local coordsys # can't be available yet, since it's never been drawn! So it presumably gives out a debug message I've been seeing # ("saved modelview_matrix is None, not using it") # and uses global modelview coords, which happen to be the same in the current test (in dna_ribbon_view.py). ###KLUGE: use ORIGIN (which we know) in place of center of view (which we don't) -- only correct when no trackballing self._newobj.motion = point_in_newobj_coords ###KLUGE since it centers new obj on mouse, even if mousedown was not centered on sample obj ###BUG (confirmed): I bet the point would be wrong in perspective view, unless we first corrected the depth plane, # then reasked for point. # trying that 070217 -- But how to fix? To correct the plane, we need to flush the DraggableObject in self._newobj, at least, # before current_event_mousepoint is likely to use correct coords (actually I'm not sure -- ###TEST) # but we can't since not all objects define .move (need to ###FIX sometime). ## self._newobj.flush() # so try this even though it might not work: ## point_in_newobj_coords_2 = self._newobj.current_event_mousepoint(plane = ORIGIN) ### but now, what do we do with this point??? # print "debug note: compare these points:",point_in_newobj_coords, point_in_newobj_coords_2 # result: identical coords. # so it doesn't work and doesn't even make sense yet... i probably can't proceed until the logic bug above is fixed. # There's also the issue of different object size on-screen if it's shown at a different depth. # (Unless that could help us somehow, in showing the depth? doubtful.) ### UI DESIGN FLAWS: the new obj is obscured, and there is no visual indication you "grabbed it", tho you did. # (actually there is one, if you wait long enough and didn't happen to grab it right on the center! but it's subtle -- # and worse, it's arguably due to a bug.) ##e would box border color change help? or box "dissolve"?? (ie temporarily remove the box) # or we might need to hide the pallette well (or make it translucent or not depth-writing) ###e need sbar messages, some message that you grabbed it (which would mitigate the visual flaw above) self._newobj.on_press( ) # needed so its on_drag and on_release behave properly when we delegate to them above return pass # end of class PalletteWell
class GraphDrawDemo_FixedToolOnArg1( InstanceMacro ): # see also class World_dna_holder -- need to unify them as a ui-provider framework # args background = Arg(Widget2D, Rect(10)) # testexpr_19a, 061207 morn -- see if arb objects work here, and try drawing on a curved surface -- # works! (when the surface was the default here -- now also tested/works when it's the arg supplied by that testexpr) # # ... except for desirable improvements: # - we should replace DZ with "local perp to surface" to make the drawn things more visible. # - And we *might* want to replace depth with "computed true depth", since for a sphere, as we rotate the view # the drawn radius slightly changes due to where the triangle faces are located, and this can bury the drawing of marks # if they are really close to the sphere -- # - either that, or record their posns relative to the sphere surface, # which might be most correct anyway -- and especially useful if we change the radius of the sphere! (making it balloon-like) # - Also those things are oriented in global coords rather than coords based on the clicked surface. # Fixing that would also make them "look oriented" and help you perceive their depth when they're over a curved surface. # # Note that all these ideas would require asking the object for the surface orientation, and for how to store coords (relative # to what), and in the given eg, getting quite different answers (incl about how to transform coords for storage, re scaling) # depending on whether the rect or sphere was clicked -- # which the current code does not even detect, since it gives them the same glname. ###e # options highlight_color = Option( Color, None ) # suggest: ave_colors(0.9,gray,white)) # use this only if background takes a color option use_VertexView = Option( bool, False ) # 070105 so I can try out new code w/o breaking old code #### TRYIT world = Option( World, World(), doc="the set of model objects") # revised 070228 for use in _19j test_background_object = Option( bool, False, doc="test the new testmode._background_object feature") #070322 hide_background_object = Option(bool, False) # internals highlightable_background = \ Highlightable( background, ####### WAIT A MINUTE, how can we do that -- background is already an instance?!? ######@@@@@@ ## background(color=green),####KLUGE, causes various bugs or weirdnesses... not yet fully understood, ## e.g. AssertionError: compute method asked for on non-Instance <Rect#10415(a)> ## [GLPane_overrider.py:455] [Highlightable.py:275] [Rect.py:52] ##Rect(5,5,green),###KLUGE2 - works now that highlightable is not broken by projection=True [also works 061213] ## background.copy(color=green), # oops i mean: ## call_Expr(background.copy,)( color=green), # oops, i have to include eval_Expr: If( highlight_color, eval_Expr( call_Expr(background.copy,)( color = highlight_color) ), # can't work unless background is simple like a Rect, # but does work then! (edit _19d to include _19 not _19b) background ## None -- I hoped None would be equivalent, noticed in HL and replaced, but that would be hard, # would require it to notice each time whether the opt was suppied or not, do default each time # rather than per-time, also require supplying None to be same as not supplying the arg (not true now) # (maybe passing some other symbol should be same as that?? and permitted each time? But If->None is so # easy, by leaving out the arg... ###e decide) ), #e want this to work too: call_Expr(background.copy, color=green), # -- just let copy take **kws and pass them on, or let it call a customize helper # review of the kluges: # - the explicit call_Expr remains annoying but the lack of simple fix still seems true; not sure; # could we experiment by turning off that check within certain classes? not sure if that's possible # since it happens during expr-building. ##k # - the need for eval_Expr seems wrong somehow, in fact i'm not sure I understand why we need it. # Will it go away in new planned eval/instantiation scheme? not sure. I think so. # - copy needs **kws. # - does the .copy itself need to be explicit? that is, could supplying args/opts to an instance copy it implicitly?? # this might fit with other instances doing other stuff when those were supplied.... ###e # - should Overlay take color option and pass it on into leaves (subexprs)? what if some leaves won't take it? # that has been discussed elsewhere... i forget if dynenv or _optional_options = dict() seemed best. on_press = _self.on_press_bg, on_drag = _self.on_drag_bg, on_release = _self.on_release_bg, sbar_text = "gray bg" ) use_highlightable_background = If( test_background_object, BackgroundObject(highlightable_background, hide=hide_background_object), DisplayListChunk( # new feature as of 070103; works, and seems to be faster (hard to be sure) highlightable_background)) ## world = Instance( World() ) # maintains the set of objects in the model _value = Overlay( use_highlightable_background, If( _self.use_VertexView, DisplayListChunk(WithViewerFunc(world, viewerfunc)), # try DisplayListChunk, 070103 later -- works, doesn't break dragging of contained old nodes. DisplayListChunk(world) ##, debug_prints = "World") ## world # zap DisplayListChunk to see if it fixes new 070115 bug about dragging old nodes -- nope )) newnode = None # note: name conflict(?) with one of those not yet used Command classes def on_press_bg(self): if 0: print "compare:" print self, self.delegate, self.delegate.delegate, self.delegate.delegate.plain # the background print self.background print "hl.highlighted =", self.delegate.delegate.highlighted # self.background is the same as the .plain printed above, which means, as of 061208 941pm anyway, # instantiating an instance gives exactly that instance. (Reasonable for now...) point = self.current_event_mousepoint() #e note: current_event_mousepoint is defined only on Highlightable, for now (see comments there for how we need to fix that), # but works here because we delegate ultimately to a Highlightable, without changing local coords as we do. # note: lots of devel scratch & debug comments removed 061207; see cvs rev 1.5 for them. # for initial test, don't use those Command classes above, just do a side effect right here ###kluge newpos = point + DZ * DZFUZZ # kluge: move it slightly closer so we can see it in spite of bg ###e needs more principled fix -- not yet sure what that should be -- is it to *draw* closer? (in a perp dir from surface) #e or just to create spheres (or anything else with thickness in Z) instead? (that should not always be required) ###BUG: DZ is not always the right direction! [more comment on that in demo_draw_on_surface.py] if not self.use_VertexView: # old code ## print "make node in old way (not using VertexView)" # still running as of 070115 at least in testexpr_19f node_expr = Vertex( newpos, Center( Rect( 0.2, 0.2, ## 'green', -- now we cycle through several colors: (colors,...)[counter % 6] ## tuple_Expr(green,yellow,red,blue,white,black)[mod_Expr(_this(Vertex).ipath[0],6)] red # the above worked ok until tested 070121 morn -- ipath now starts with string. # it was a kluge anyway, so disable it until we can rework it to be sensible. ))) else: # new code, being written 070105, just getting started -- mostly nim node_expr = Vertex_new( newpos, # cycle through several colors: (colors,...)[counter % 6] color=tuple_Expr(green, yellow, red, blue, white, black)[mod_Expr(_this(Vertex).ipath[0], 6)]) pass ## draggable_node_expr = Highlightable(node_expr, on_drag = _self.on_drag_node, sbar_text = "dne") ###BUG: this breaks dragging of the new node; it fails to print the call message from on_drag_node; # if you try to drag an old node made this way, it doesn't work but says # debug fyi: len(names) == 2 (names = (268L, 269L)) # Guess: limitation in current rendering code makes it not work for any nested glnames, but just print this instead... # (note: even after reload, the node objects in the world have their old Vertex class, and the old expr used to make them) # # [later 061213:] IIRC the status was: I made GLPane_overrider so I could fix that 2-glname issue in it, # but never got to that yet. Meanwhile I commented out the use of this expr, and thus on_drag_node is never used... # and Vertexes dragged directly do nothing -- they're highlightable but with no actions. # And World could probably draw them highlightable even if they weren't, but it doesn't. # BTW the disabled nonworking draggable_node_expr is not well-designed -- it does not add a Vertex to World, it adds a # draggable one -- but even World is not perfect, since it contains Vertexes (not just their data) # and they inherently have (a lack of) action bindings since they are Highlightable. # Probably better would be if World contained data-nodes and had access to (or had its own) display rules for them # which added commands/actions based on the currently active tools. That would help with tool-code-reloading too. # Probably some other comments here say this too. # # So does a World need a formula or function arg for how to map its data objects to display objects, at the moment? # Or is there some scheme of a global map for that, to be applied when "drawing" any data object? # And do some data objs have their own positions, or is that always supplied by the world or other data obj they're in? # In theory, we might display atoms at posns unrelated to atom.pos, e.g. as a row in a table which includes their coords. # So it's more like we have ways of "drawing a set of things" which can say "at posns given by func(thing)" # or "at successive posns in a column", corresponding to two display forms with different exprs, # with the map from thing to individual display form also needing to be specified. # So a World is more like a set of things, and it can have a display mode (or more than one), given a thing-display-function. # We can ask it or anything else how it recommends displaying itself given display style options, # but we can choose to use that display function (from it to a more directly displayable object) or use another one. # Or we can probably just "draw it" and have it pick up the current display style from the env (including the # currently active tools). Is there any reason not to permit both? (draw using current style, draw using given style, # give me function from you to drawables using given style, use specific function and draw the results -- all possible.) # # If a thing in a world has standard mouse actions of its own, can it also have "grabbable areas" for use in dragging it # when it has a posn as displayed in some world? Or did that world have to explicitly turn it into a draggable thing? # Answer: both. The world turns it into that by adding a drag binding for those "overall handles" the thing has. # It might draw them with glnames in some set it knows... ie as named subobjs of itself. The overall thing might also # have a single name. Then we have a sequence of two glnames meaning obj/subobj which we want to use to determine the action. # For some subobjs that's within the object and supplied by it (perhaps depending on tool); for others, # it's supplied by the World it's in (also dep on a tool) and handled by it (eg move the obj, select the obj). # # For the simple things we have there, there are no subobjects, and no actions except drag or later select the whole thing. # A simple model is "one thing was hit, but some things are specified by a specific series of two or more glnames". # In general the outer name decides how to interpret (or whether to ignore) the inner names. # It can map the inner ones somehow... not sure how. This will relate a lot to DisplayListChunk when we have that. # Mere nested Highlightables might push two names but both would be unique. Outer name might just defer to inner one then. if 0: ## MAKE THIS WORK: draggable_node_expr = 'define this' newnode = self.world.make_and_add(draggable_node_expr, type="Vertex") else: newnode = self.world.make_and_add( node_expr, type="Vertex") #070206 added type = "Vertex" self.newnode = newnode ###KLUGE that we store it directly in self; might work tho; we store it only for use by on_drag_bg return # from on_press_bg ## def on_drag_node(self): ## print "on_drag_node called -- how can we know *which* node it was called on??" ## # 070103 status guess: this is not called; old cmts above seem to say that the only problem with it working is nested glnames. ## return def on_drag_bg(self): # note: so far, anyway, called only for drag after click on empty space, not from drag after click on existing node point = self.current_event_mousepoint() lastnode = self.newnode # btw nothing clears this on mouseup, so in theory it could be left from a prior drag ## try: ## lastipath = lastnode.ipath[0] ## except: ## lastipath = -1 ## # print "on_drag_bg %d" % lastipath, point### # this shows no error in retaining correct lastnode -- that's not the bug ## print "on_drag_bg" newpos = point + DZ * DZFUZZ # used for different things, depending what = kluge_dragtool_state() ###IMPLEM better if what == 'draw': # make a blue dot showing the drag path, without moving the main new node (from the click) node_expr = Vertex(newpos, Center(Rect(0.1, 0.1, blue))) self.world.make_and_add( node_expr, type="dot" ) #070206 added type = "dot" -- note, not deducible from the expr!! elif what == 'polyline': if not lastnode: print "bug: no self.newnode!!!" else: if not isinstance(lastnode, polyline): lastnode = self.newnode = self.world.make_and_add( polyline(lastnode), type="polyline") lastnode.add_point(newpos) elif what == 'drag': # drag the new node made by the click if not lastnode: print "bug: no self.newnode!!!" else: lastnode.pos = newpos pass return def on_release_bg(self): #070223 new hack import foundation.env as env #FIX if isinstance(self.newnode, polyline) and env.prefs.get( kluge_dragtool_state_prefs_key + "bla2", False): self.newnode._closed_state = True ####KLUGE, I'd rather say .closed but that won't work until I have OptionState return # == methods make, make_and_add have been moved from here into class World [070202] pass # end of class GraphDrawDemo_FixedToolOnArg1
class Vertex( ModelObject ): # renamed Node -> Vertex, to avoid confusion (tho it added some too, since localvars not all changed, # and since the world ought to contain model objs anyway, in general, of which Vertex is only one type, and current implem # also has its own graphics code)... # see below about better ways to rename it... """It has a position, initializable from arg1 but also settable as state under name pos, and an arb appearance. Position is relative to whatever coords it's drawn in. When you drag it, it chooses depth just nearer than hitpoint -- which means if you wiggle it over itself, it gets nearer and nearer (design flaw). [see comment for possible fix] """ # 070223 possible fix to seeing depth of self: during a drag, tell glpane not to draw dragobj into depth or during glselect # mode... so it finds some other selobj besides dragobj. means it should draw dragobj last i guess... maybe transparent # so you can see selobj behind it, or diff color when there is a selobj... ###e about what to rename it... Hmm, is it a general "Draggable"? .lookslike arg2 -> .islike or .thing arg1? # BTW, the highlightability is not yet specified here ###nim; it relates to the fact that we can bind commands to it # (as opposed to things it contains or is contained in, tho that should be permitted at the same time, # tho implem requires nested glnames to sometimes ignore inner ones, when those are inactive); # but we might add it in a wrapper which makes it into a view of it that lets commands be bound to it, # except that World.draw assumes we draw the actual model objs, not wraps of them - also an issue if env says they # look different -- so maybe World needs to wrap all it draws with glue to add looks and behavior to it, in fact. # [update much later 070201: or maybe ModelObject knows about this and asks env whether to override default drawing code.] # but for now, can we do this here? pos0 = Arg(Position) pos = State( Position, pos0 ) ###BUG -- does this work -- is pos0 set in time for this? not sure it's working... 061205 1009p #e we probably want to combine pos0/pos into one StateArg so it's obvious how they relate, # and only one gets saved in file, and only one self.attr name is used up and accessible # the rest of this class is for drawing it and interacting with it. # Can we move that code into e.g. VertexDrawable or VertexView # and have Vertex itself just delegate (for sake of draw, lbox, etc) to some rule in the env for getting a Drawable for it, # probably looking it up in a dynamic memoizing mapping in the env? ####e [070105] # I think YES. # - a main difference between a model & view object -- a view obj knows how to draw itself, a model obj asks the env. # (and the answer is quite different in different envs, e.g. 3d area vs MT. but what if we mix those?) # related notes: # - it might want a different delegate for drawing purposes (draw, lbox) and sim purposes (or others). # - does it also have an attr corresponding to its index/address/id in the model world? # - and maybe some for relations to other model objs, eg MT-parent, tags, name, etc? # - and maybe some graphics prefs like color or whatever, even if it's up to the drawing rule whether/how to use these? # - and for that matter some arbitrary other data (named attrs), as we'll let any model object have? # simplest thing that could work -- # - make some sort of MemoDict for model-view and put it into the env, even if we kluge how to set it up for now. # how does demo_MT do it? With a hardcoded helper function using a MemoDict in a simple way -- no involvement of env. # So see above for copied code from that... ###e #e so now what we want is for this (in super modelobject) to delegate to a computed viewer from the helper func above #e but also to have more args that can be used to set the color [do this only in Vertex_new, below] ## color = Option(Color, color) ## pass ## class VertexView(xx): -- SOMETHING LIKE THIS IS THE INTENT #### #e and then we want this new class to have the hardcoded appearance & behavior which the code below now passes in lookslike arg lookslike = ArgOrOption(Anything) # OrOption is so it's customizable ###BAD for lookslike to be an attr of the Vertex, at least if this should be a good example of editing a sketch. [070105] # (a fancy sketch might have custom point types, but they'd have tags or typenames, with rules to control what they look like) cmenu_maker = State( Anything, None ) # meant to be set from outside; let's hope None is a legal value [070223 HACK] ## delegate = _self.lookslike #k delegate = Highlightable( Translate(lookslike, pos), ## eval_Expr( call_Expr(lookslike.copy,)( color = yellow) ), #####?? weird exc don't know why - reload? ## AssertionError: _this failed to find referent for '_this_Vertex' on_drag=_self.on_drag, # 070103 kluge experiment, works (eg in _19d) cmenu_maker= cmenu_maker #070223 HACK [#e rename cmenu_maker to cmenu_obj as I guessed it was named??] ) #e actions? or do this in a per-tool wrapper which knows the actions? # or, do this here and make the actions delegate to the current tool for the current parent? guess: the latter. ##e digr: will we want to rename delegate so it's only for appearance? *is it*? does it sim like this too? # Guess: it's for everything -- looks, sim qualities, etc -- except what we override or grab from special members. # [if so, no need to rename, except to clarify, leaving it general.] #e might need something for how to save it in a file, Undo policy, etc def on_drag( self ): # 070103 kluge experiment, copied/modified from on_drag_bg in bigger class below # Note: this bug reported in BUGS breaks dragging of these nodes; worked around by increasing DZFUZZ: # 070115 "highlightable-finder can be fooled due to check_target_depth_fudge_factor of 0.0001" # where i am 070103 447p (one of two places with that comment) # - sort of sometimes works, but posns are sometimes same as bg, not sure why that DZ would be needed, # oh i guess the mousepos comes from the gray bg rect not the self unless we drag very slow... point = self.current_event_mousepoint( ) ### MIGHT BE WRONG COORDS? guess: no #k guess: will have snap-to-fixed-point bug for generic drag code newpos = point + DZ * DZFUZZ # used for different things, depending #### DZ needed but might cause trouble too self.pos = newpos ## print "in-node action set %r.pos = %r" % (self, newpos) # sometimes works self.KLUGE_gl_update() #k needed? return pass # end of class Vertex
class ActionButton(DelegatingInstanceOrExpr): # 070104 quick prototype "ActionButton(command, text) is something the user can press to run command, which looks like a button." # args/options command = Arg(Action) #e default which prints? text = Arg(str, "<do it>" ) #e default text should be extracted from the command somehow button = Arg( Widget2D, Rect(15. * PIXELS) ) # can it be left out so only text label is used? ideally we'd have text with special border... enabled = Option( bool, True ) # whether the button should look enabled, and whether the command will run when the button is operated # formulae use_label = TextRect(text) ## TextRect(text,1,20)###e revise plain_button = CenterY(button) highlighted_button = Boxed( plain_button, # note: despite the name, this is only shown as the highlighted form when enabled is true bordercolor= blue, # should color adapt to bg? is it a bad idea to put this over bg rather than over button? borderthickness=1.5 * PIXELS, gap=1 * PIXELS, ) ###k ???? -- note, this doesn't include the label -- ok? plain = DisplayListChunk(SimpleRow( plain_button, CenterY(use_label))) # align = CenterY is nim highlighted = DisplayListChunk( SimpleRow(highlighted_button, CenterY(use_label), pixelgap=0.5)) #k ok to wrap with DisplayListChunk? [seems so] ### KLUGE: without the pixelgap adjustment (to this particular weird-looking value, i guess), # the label moves to the right when highlighted, due to the Boxed being used to position it in the row. ### BUG: CenterY is not perfectly working. Guess -- lbox for TextRect is slightly wrong. ### IDEA: make the borderthickness for Boxed negative so the border is over the edge of the plain button. Might look better. ##e Note: we have no "pressed" appearance, since by the next time we get drawn, the command is already drawn and we ought to be # back to normal. Someday we should do a transient incremental redraw of just this button, with a "pressed and acting" appearance, # which can then go back to normal when the operation completes and does a regular full redraw. # Alternatively, we could switch to using buttons with an on_release_in action only, # and then have ordinarily-drawn pressed etc looks. [070227 comment] # update 070305: let's try to fix that: # appearances for optional willdoit-flicker (confirms unambiguously that the button was hit and will do something) [070307] # [ideally the computation & side effects could overlap the willdoit flicker in time, # but they don't now, which is one reason the flicker is optional] incr_drawable_willdo1 = Instance( SimpleRow(highlighted_button(bordercolor=yellow), pixelgap=0.5)) # label not needed here incr_drawable_willdo2 = Instance( SimpleRow(highlighted_button(bordercolor=blue), pixelgap=0.5)) # note: yellow/blue (matching the usual ending & starting colors which bracket the flicker) looks much better than black/white # what it looks like while we're computing/doing its effects: incr_drawable_doing = Instance( SimpleRow( highlighted_button(bordercolor=orange), ## CenterY(use_label), [removed -- see comment for why -- might be added back] pixelgap=0.5)) # orange warns you that it's not yet done, is also bright & active for action; ### UI FLAW: the orange/yellow distinction is annoying, so it's really only desirable for debugging, # since it shows that the instantiation time is significant but only happens on the first use of a button. # Probably the distinction (and its redraw happening at all) should be a debug_pref or so. ###FIX # (But if there is no distinction, we may want to be sure to redraw the label now if there is any chance it can be different -- # but in current code there's not, since we haven't changed state it might depend on by the time we draw it. # BTW I wonder if redrawing the label (i.e. instantiating this instance of it) ever takes significant time itself?? #k) # what it looks like while we're redrawing (after finishing its internal effects): incr_drawable_done = Instance( SimpleRow(highlighted_button(bordercolor=yellow), pixelgap=0.5)) # label not needed here # yellow means done -- not sure makes sense -- note green means "can do" in some controls def doit(self): """This runs when the user clicks on the button. WARNING: it's NOT just self.do_action() (the public method self's clients, like scripts, can call to do the same action) -- it calls that, but it also has graphical effects. [It may or may not be public (in the Action interface) in the end. If it is, it'll be renamed. #e] """ if self.enabled: # do some incremental drawing [new feature 070305, revised 070307] ###BUG (in some client code of this class): # this won't be able to make clear button quickly show it's disabled until client code is revised and maybe selobj-bugfixed ###DOIT if debug_pref( "testmode: ActionButton willdoit-flicker?", # When set, this flickers the button, like how the mac confirms a menu item choice. # Conclusion after testing: it works fine, and usually looks ok, # but is redundant with "yellow during redraw", # so as long as that's slow enough to see, this has no point and is also making it even slower, # so leave it turned off by default. Choice_boolean_False, prefs_key='A9 devel/exprs/action flicker'): # works [retest###], but I won't make it True by default [070307] ##e someday do this in a way that does not tie up the thread during this, e.g. by letting paintGL do it; # for now it's just experimental for its graphics effects and as a speed test, # and will probably be turned off after testing for i in range(4): if i % 2 == 0: self.draw_incrementally(self.incr_drawable_willdo1) else: self.draw_incrementally(self.incr_drawable_willdo2) # print i, # very fast # todo: delay, if needed to make this visible -- using time.time to delay only if draw timing was not long enough # (with no delay it's almost too fast too see -- sometime I should write the code to measure the actual speed) # (for now assume it's always very fast, so just delay a fixed amount using time.sleep) time.sleep(1.0 / 3 / 4) # 1/3 sec, spread over 4 sleeps self.draw_incrementally( self.incr_drawable_doing ) # this method runs in the Highlightable made in delegate print "ActionButton: doing %r for %r" % (self.text, self ) ### remove self? ##e optim note: this shows self is a different obj each time (at least for make dna cyl button)... # I guess this is due to dna_ribbon_view_toolcorner_expr_maker being a function that makes an expr # which runs again at least on every use of the button (maybe more -- not sure exactly how often). # Should fix that (and it's not this file's fault -- just that the print stmt above reveals the problem). self.do_action() self.draw_incrementally(self.incr_drawable_done) pass else: print "ActionButton: not enabled, so not doing %r for %r" % ( self.text, self) # remove when works [reenabled 070307 ####] pass return def do_action(self): "#doc -- public, also used internally; see doit comment for doc, for now" res = self.command() if res is not None: print "unexpected: %r cmd %r retval was not None: %r" % ( self, self.text, res, ) #e remove if happens legitimately return ###e refile these: def draw_incrementally( self, thing): #070307 #e refile (as for next method below) "#doc" self._incrementally_draw_OpenGL( thing.draw ) #e or call a variant method of thing, which defaults to thing.draw?? nah, use an env var? def _incrementally_draw_OpenGL( self, func ): #070307 #e rename ###e refile into IorE someday, and into Highlightable for now, i think """helper method for incremental drawing by user event handling methods (not part of self.draw called by paintGL). [#doc better] func should contain OpenGL commands for incrementally drawing, in self's coords (but not the swapbuffers at the end). Guess at a requirement within func: [which should be removed and is prob not real now, see below] # don't use drawkid! (because we're not inside a draw method) # (but will this cause trouble for draw methods inside this?? ### NEEDS REVIEW) [but instead we might as well make sure that drawkid's parent-seeing alg will not be messed up, since it'll be used inside whatever subthing draws we call, anyway] """ ###e undefined in API so far: what if func says "draw later" (eg for transparency) -- do we do all that too, before we return?? # guess: yes, but we'll need special drawing-env settings to tell primitives inside func that we're doing incremental drawing, # since it'll affect things like whether it's ok to write into the depth buffer for transparent objs obscuring visible ones # (useful for glselect code but would mess up subsequent incr drawing). def func1(self=self, func=func): res = func() self.env.glpane.swapBuffers() # update display [needed] return res ran_already_flag, funcres = self.run_OpenGL_in_local_coords(func1) # note: this runs in self or first delegate that's a Highlightable, for now; that determines its gl state & coordsys assert ran_already_flag return funcres #e should we change to doing the action on_release_in, rather than on_press? delegate = Highlightable( plain, ##e should this depend on enabled? probably yes, but probably the caller has to pass in the disabled form. ###e at least for the Mac, maybe it also ought to depend on whether the application is active (frontmost) and will respond to clicks. If( enabled, highlighted, plain ), # revised 070109 to depend on enabled [#k does this cause the delegate expr itself to be remade??] on_press=_self.doit, # note: there was a bug in the prior form of this, "on_press = command" -- command to do should depend on enabled -- ##e but i'm not sure if If(enabled,command,None) will work properly ###k TRY IT -- nevermind, using _self.doit now [070208] sbar_text=text #e should sbar_text depend on enabled?? yes, but need to revise callers then too -- some of which make the text depend on it ) pass # end of class ActionButton
class _MT_try2_node_helper(DelegatingInstanceOrExpr): """ [private helper expr class for MT_try2] One MT item view -- specific to one node, one whole MT, and one (possibly time-varying) position with it. """ # args ####e REORDER THEM node = Arg(ModelNode, doc = "any node that needs to be displayed in this MT") ###e NOTE: type coercion to this is nim; while that's true, we use helper functions like node_name(node) below; # once type coercion is implemented # (or simulated by hand by wrapping this arg with a helper expr like ModelTreeNode_trivial_glue), # we could instead use node.mt_name, etc.) mt = Arg(MT_try2, doc = "the whole MT view, in which we store MT items for nodes, and keep other central state or prefs if needed") name_suffix = Option(str, "") initial_open = Option(bool, False, doc = "initial value of boolean state 'open'; only used when this item is first created") ##e should ask the node itself for the initial value of open (e.g. so new groups, trying to start open, can do so), # and also advise it when we open/close it, in case it wants to make that state persistent in some manner # WARNING: compare to MT_try1 -- lots of copied code after this point # WARNING: the comments are also copied, and not yet reviewed much for their new context! (so they could be wrong or obs) ###k # state refs open = State(bool, initial_open) # other formulae ###e optim: some of these could have shared instances over this class, since they don't depend on _self; should autodetect this # Note, + means openable (ie closed), - means closable (ie open) -- this is the Windows convention (I guess; not sure about Linux) # and until now I had them reversed. This is defined in two files and in more than one place in one of them. [bruce 070123] open_icon = Overlay(Rect(0.4), TextRect('-',1,1)) closed_icon = Overlay(Rect(0.4), TextRect('+',1,1)) openclose_spacer = Spacer(0.4) #e or Invisible(open_icon); otoh that's no simpler, since open_icon & closed_icon have to be same size anyway # the openclose icon, when open or close is visible (i.e. for openable nodes) openclose_visible = Highlightable( If( open, open_icon, closed_icon ), on_press = Set(open, not_Expr(open)), sbar_text = getattr_Expr( _self, '_e_serno') #070301 this permits finding out how often MT gets remade/shared # (results as of 070301: remade when main instance is, even if going back to a prior testexpr, out of _19i & _30i) ) openclose_slot = If( call_Expr(node_openable, node), openclose_visible, openclose_spacer ) if 0: # cross-highlighting experiment, 070210, but disabled since approach seems wrong (as explained in comment) yellow = DZ = 'need to import these' indicator_over_obj_center = Center(Rect(0.4, 0.4, yellow)) position_over_obj_center = node.center + DZ * 3 ###BUG: DZ does not point towards screen if trackballing was done ###STUB: # - should be drawn in a fixed close-to-screen plane, or cov plane (if obscuring is not an issue), # - so indicator size is constant in pixels, even in perspective view (I guess), # - also so it's not obscured (especially by node itself) -- or, draw it in a way visible behind obscuring things (might be a better feature) # - what we draw here should depend on what node is # - we also want to draw a line from type icon to node indicator (requires transforming coords differently) # - needs to work if node.center is not defined (use getattr_Expr - but what dflt? or use some Ifs about it) pointer_to_obj = DrawInCenter( Translate( indicator_over_obj_center, position_over_obj_center)) #bug: Translate gets neutralized by DrawInCorner [fixed now] ###BUG: fundamentally wrong -- wrong coord system. We wanted DrawInAbsCoords or really DrawInThingsCoords, # but this is not well-defined (if thing drawn multiply) or easy (see comments about the idea in projection.py). else: # What we want instead is to set a variable which affects how the obj is drawn. # If this was something all objs compared themselves to, then all objs would track its use (when they compared) # and therefore want to redraw when we changed it! Instead we need only the involved objs (old & new value) to redraw, # so we need a dict from obj to this flag (drawing prefs set by this MT). Maybe the app would pass this dict to MT_try2 # as an argument. It would be a dict of individually trackable state elements. (Key could be node_id, I guess.) # ### TRY IT SOMETIME -- for now, cross-highlighting experiment is disabled. pointer_to_obj = None # selection indications can use this node_is_selected = call_Expr( mt_node_selected, node) kluge_icon_color = If( node_is_selected, blue, green) sbar_format_for_name = If( node_is_selected, "%s (selected)", "%s") ###STUB for the type_icon ##e the Highlightable would be useful on the label too icon = Highlightable( Rect(0.4, 0.4, kluge_icon_color), ##stub; btw, would be easy to make color show hiddenness or type, bfr real icons work Overlay( Rect(0.4, 0.4, ave_colors(0.1, white, kluge_icon_color)), #070216 mix white into the color like DraggableObject does pointer_to_obj ), sbar_text = format_Expr( sbar_format_for_name, call_Expr(node_name, node) ) ) ##e selection behavior too label = DisplayListChunk( # added DisplayListChunk 070213 late -- does it speed it up? not much; big new-item slowness bug remains. retain, since doesn't hurt. TextRect( call_Expr(node_name, node) + name_suffix ) ) ###e will need revision to Node or proxy for it, so node.name is usage/mod-tracked ##e selection behavior too -- #e probably not in these items but in the surrounding Row (incl invis bg? maybe not, in case model appears behind it!) ##e italic for disabled nodes ##e support cmenu delegate = SimpleRow( CenterY(openclose_slot), SimpleColumn( SimpleRow(CenterY(icon), CenterY(label)), #070124 added CenterY, hoping to improve text pixel alignment (after drawfont2 improvements) -- doesn't work If( open, _MT_try2_kids_helper( call_Expr(node_kids, node) , _self.mt ), # 070218 added _self.mt -- always intended, first used now None # Note: this None used to be Spacer(0), due to a bug mentioned in a comment in ToggleShow.py # (but unfortunately not explained there -- it just says "I wanted None here, but it exposes a logic bug, # not trivial to fix, discuss in If or Column" -- my recollected bug-theory is described just below). # On 070302 I confirmed that None seems to work (even in testexpr_18i with a group of 2 chunks, plus two more below). # I don't fully know why it works, since I thought the bug was that SimpleColumn's None specialcase # didn't run, since the element was not None but the If, and then delegating lbox attrs to None didn't work. # (Fixable by using the newer If that evals, but for some reason that's not yet standard, I guess just because # I didn't have time to test it enough or think it through fully re ipath or instance caching or something.) # But as long as it works, use it -- ask Qs later. A recent perhaps-related change: None is allowed in drawkid. # (A memory scrap -- does instantiating None conceivably produce a spacer?? ###k) ) ) ) pass # end of class _MT_try2_node_helper