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
def checkbox_pref_OLDER( prefs_key, label, dflt=False): # renamed 061215 late, since newer one is better "#doc" if type(label) == type(""): label = TextRect(label, 1, 20) return SimpleRow(CenterY(checkbox_v3(PrefsKey_StateRef(prefs_key, dflt))), CenterY(label)) # align = CenterY is nim
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