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
def ChoiceRow( nchoices, dflt = 0, **kws): ##e should be an InstanceMacro, not a def! doesn't work to be customized this way. "#doc" #e nim: Row -- esp on list like this -- Row( map(...)) -- so use SimpleRow return Apply( lambda stateref_expr, nchoices = nchoices, dflt = dflt, kws = kws: SimpleRow( # stateref_expr will be a Symbol when this lambda is run, to produce an expr, once only SimpleRow( * map( ChoiceButton(choiceref = stateref_expr, **kws), range(nchoices) ) ), # choose TextRect( format_Expr( "choice is %%r (default %s)" % dflt, stateref_expr.value ), 1, 30) # show choice ), LocalVariable_StateRef(int, dflt) # LocalState, combining this and the Apply? # or, just a stateref to some fixed state somewhere... whose instance has .value I can get or set? use that for now. ##k is there any way to use the new-061203 State macro for this? )
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
class PixelTester( InstanceOrExpr, DelegatingMixin ): # ought to be InstanceMacro but trying this alternate style just to see it # args testexpr = Arg(Widget2D) # instantiated right here, hope that's ok testname = Arg(str) # required for now, used to form filename # value filename = format_Expr("/tmp/%s.jpg", testname) delegate = SimpleColumn( TextRect( "saved image from PRIOR session, in blue box" ), # kluge: execute this first, so we read file before writing it Boxed(bordercolor=blue)(Image(filename)), Spacer(0.3), TextRect("live widget, in purple box"), Boxed(bordercolor=purple)(PixelGrabber(testexpr, filename)), ##e and put current session image here, for comparison, or put top on in a tab control for flicker test ) pass # end of class PixelTester
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 _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
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