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 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 test_StateArrayRefs_2(DelegatingInstanceOrExpr): # testexpr_35a indices = range(4) ## heights = StateArrayRefs(Width, ORIGIN) ###KLUGE for now: this contains heights * DZ as Vectors, not just heights heights = StateArrayRefs(Width, 0.0) def _height_dragger_for_index(self, index): stateref = StateArrayRefs_getitem_as_stateref(self.heights, index) #e change to self.heights.getitem_as_stateref(index)? self.heights._staterefs[index]?? self.heights[index]??? newindex = ('_height_dragger_for_index', index) return self.Instance(_height_dragger(stateref), newindex) delegate = SimpleRow( MapListToExpr( _self._height_dragger_for_index, ###k _self needed?? indices, KLUGE_for_passing_expr_classes_as_functions_to_ArgExpr( SimpleColumn)), #e SimpleGrid? 2d form of MapListToExpr? ActionButton( _self.printit, "button: print state" ) ###e idea: define on special attr, let UI assemble debug info viewer ) def printit(self): #e can PrintAction do this for us? print[ h.value for i, h in sorted_items(self.heights) ] ###KLUGE, assumes they're StateRefs -- maybe just rename StateArray -> StateArrayRefs pass
class test_StateArrayRefs_3( DelegatingInstanceOrExpr): # testexpr_35b, _35c indices = range(3) heights = StateArrayRefs(Width, 0.0) direction = Arg(Vector, DX, "direction of permitted motion -- DZ is the goal but DX is easier for testing") ### DX for initial test (testexpr_35b), then DZ (testexpr_35c) range = Option(tuple_Expr, None, doc = "range limit of height") msg = Option(str, "drag along a line") def _height_dragger_for_index(self, index): stateref = StateArrayRefs_getitem_as_stateref( self.heights, index ) #e change to self.heights.getitem_as_stateref(index)? self.heights._staterefs[index]?? self.heights[index]??? newindex = ('_height_dragger_3_for_index', index) return self.Instance( _height_dragger_3( stateref, self.direction, sbar_text = "%s (#%r)" % (self.msg, index,), range = self.range ), newindex ) delegate = SimpleRow( MapListToExpr( _self._height_dragger_for_index, ###k _self needed?? indices, KLUGE_for_passing_expr_classes_as_functions_to_ArgExpr(SimpleColumn) ), #e SimpleGrid? 2d form of MapListToExpr? ActionButton( _self.printit, "button: print state") ###e idea: define on special attr, let UI assemble debug info viewer ) def printit(self): #e can PrintAction do this for us? print [h.value for i,h in sorted_items(self.heights)] ###KLUGE, assumes they're StateRefs -- maybe just rename StateArray -> StateArrayRefs pass
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 ChoiceRow_class(InstanceMacro): # stub, nim nchoices = Arg(int) dflt = Arg(int, 0) kws = OptionsDict() ###IMPLEM #e see if name is correct re other proposals, eg Options not Option? #e renamekws tosay what they are for -- the ChoiceButton --- note that we could ditch the OptionsDict and ask for # an explicit dict of them, but then they would not be indivly customizable. Should a new sub-option cust scheme be made? #e var = State(int, _self.dflt) ###k does _self.dflt work? Is this equiv to the above? NO, it's an lval not a stateref! _value = SimpleRow( 0 ###stub -- the next hard thing is to apply this to a variable number of exprs or insts created by map over range(nchoices) ) pass
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 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
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 main_ui_layout(DelegatingInstanceOrExpr): #e rename? is it not only the ui, but the entire app? (selection, files, etc) #e merge in the App obj from test.py, and the _recent_tests system in some form? # args (none yet) # internal state - permanent ###e note: when we reload and remake this instance, we'd prefer it if the world state stayed unchanged (as i presume it does) # but if the default_tool instance and toolstack state got remade. The lack of the latter has been confusing me # since changes to ui code aren't working. I think this is a difference between a ui and operations layer (should change) # vs model data layer (should not change even tho the op methods on it can change). So when I can put these things into layers # (not only State, but even Instance or attrs within them) and make those sensitive to reload, that will help. # In the meantime -- if I could kluge Instance and State to take an option to control this # (like index = exprs_globals.reload_counter) # it might help.... #####TRYIT SOMETIME, and BE CAREFUL UNTIL I DO. world = Instance(World()) default_tool = Instance(DefaultToolRun()) # internal state - varying toolstack = State(list_Expr, [ default_tool ]) # always has at least one tool on it; a stack of Instances not exprs # maybe the better term for this is something like command & subcommand current_tool = toolstack[ -1] # last tool on the stack is current; exiting it will pop the stack (Instance not expr) ##e (add a type-assertion (as opposed to type-coercion) primitive, so I can say "this is an Instance" in the code?) # NOTE: this is not strictly speaking a tool, but ONE RUN of a tool. That might be important enough to rename it for, # to ToolRun or maybe ActiveTool or RunningTool or ToolInUse or ToolBeingUsed... # [but note, obj might remain around on history or in Undo stack, even when no longer being used], # since we also have to deal with Tools in the sense of Tool Run Producers, eg toolbuttons. ###e # parts of the appearance registry = find_or_make_global_command_registry( ) ## None ###STUB, will fail -- ## AttributeError: 'NoneType' object has no attribute 'command_for_toolname' toolstack_ref = None ###STUB toolbar = Instance( MainToolbar(registry, ["Features", "Build", "Sketch"], toolstack_ref)) #e arg order? ###e args/opts for what tools to show -- maybe their cmdnames & it loads them from elsewhere #e add row of tool buttons, and flyout toolbar; use ChoiceRow?? the things should probably look pressed... # they might need cmenus (find out what the deal is with the cmenus i see in the ui mockup - related to flyouts? # yes, it's like this: main tools have cmenus with subtools, and if you pick one, main tool and its subtool both look pressed # I'll need new specialized controls.py classes for these; new super Control for all kinds of controls?? (not sure why...) propmgr = SimpleColumn( TextRect( "(property manager)"), #e possibly to become a tab control tab DebugPrintAttrs( current_tool.property_manager ) # must be None if we don't want one visible; otherwise an Instance ###BUG: DebugPrintAttrs shows that it's a spacer -- I guess IorE turns None into one when it instantiates? Make it a false one?? ) mt = SimpleColumn( TextRect( "(model tree)" ), #e possibly to become a tab control tab, but only when we're in the left channel MT_try2(world) #e rename to "Feature Manager" ?? ##e soon, MT should be not on whole world but on model or cur. part, a specific obj in the world ) graphics_area = _self.world ##e ditto for what we show here, except it might not be the exact same object, and it will really be shown in a way # that depends on both the current display style and the current tool (command & subcommand) graphics_area_topright_buttons = current_tool.graphics_area_topright_buttons # overall appearance delegate = Overlay( # stuff in the corners -- note, these don't use the corner constants for standalone tests like PM_CORNER DrawInCorner(corner=UPPER_LEFT)( SimpleColumn( toolbar, #e add tab control SimpleRow( If( current_tool.property_manager, Top(propmgr), None ), #k None?? prob ok now, see demo_MT comment 070302 ###k Top(mt) ), #e actually we'd then put a splitter & glpane-like-thing... #e anything just below the propmgr? )), DrawInCorner(corner=UPPER_RIGHT) ( ##e of graphics area, not entire screen... graphics_area_topright_buttons ### WRONG, these should go under the main toolbar area on the right # (but we don't yet have any 2dwidgets which expand to fill the available space, except DrawInCorner of entire screen) # (this won't matter once the toolbar is done entirely in Qt, so we don't need to correct it for now) ), #e other corners? "... an area (view) on the right side # of the main window for accessing the part library, on-line documentation, etc" # the main graphics area #e [this too ought to go under the toolbar and to the right of the propmgr, but that can wait until they're fully in Qt] graphics_area) pass
class MainToolbar(Toolbar): ###e how is the Main one different from any other one??? not in any way I yet thought of... # unless its flyout feature is unique... or maybe it has the behavior of deciding what to look like, inside it?? # Nah, its client needs to provide the spec that, even if MainToolbar then does the work... but if it *can* do the work, # that might count... maybe it's just that it has no parent toolbar? #e rename """The main toolbar that contains (for example) Features, Build, Sketch, Dimension (tool names passed as an arg), with their associated flyout toolbars, and maintains the state (passed as a stateref) of what tool/subtool is active. """ # args registry = Arg( CommandRegistry, doc = "the place in which tool name -> tool code mapping is registered, and in which subtools are found") #e the app object in which the tools operate? #e the world which they affect? toolnames = Arg(list_Expr, doc = "list of names of main tools") toolstack_ref = Arg(StateRef, doc = "external state which should be maintained to show what tool & subtool is active now") # formulae # appearance delegate = SimpleRow( MapListToExpr( _self.toolbutton_for_toolname, toolnames, KLUGE_for_passing_expr_classes_as_functions_to_ArgExpr(SimpleRow) ), TextRect("flyout goes here") ) def toolbutton_for_toolname(self, toolname): assert type(toolname) == type("") registry = self.registry ## expr = Boxed(TextRect(toolname)) # stub #e look up commands from registry ### LOGIC BUG: don't we get the toolname/cmd pair from the reg? if so, # then at this stage, just look up cmd from a local cache we made of cmds that go with our names for them. # But in current code, toolnames were passed in. Nevermind. command = registry.command_for_toolname(toolname) ###STUB - at least since retval might be None # [also, terms are messed up -- straighten out cmd vs tool, use same for main and sub] # [maybe: a command is something you do, and a command is "invoke a tool", ie start it (does not imply finishing it) -- # but i don't like that much -- what i look up here is the longlived-thing-maker (toolrun maker), # not a subr that invokes it. otoh what abt toolbuttons that have immediate effect, no longlived thing created? # their presence means i *do* have to look up a command, which when run *might* change toolstack state.] subtools = registry.subtools_for_command(command) ###STUB?? expr = MainCommandToolButton( self, toolname, command, subtools) #e is it ok about MapListToExpr that it makes us instantiate this ourselves? Can't it guess a cache index on its own? #e related Q: can we make it easier, eg using nim InstanceDict or (working) _CV_ rule? instance = self.Instance( expr, "#" + toolname) return instance def _advise_got_pressed(self, button): print "my button %r with toolname %r says it got pressed" % (button, button.toolname) ###STUB - do the following: #e decide if legal at this time #e set it as the next running button #e unpress other buttons (and finish or cancel their runs if needed) (maybe only if their actions are incompat in parallel??) # e.g. something like: ## while toolstack_ref.get_value()[-1].is_incompatible_with(something): ## didit = toolstack_ref.get_value()[-1].force_finish(something) ## if not didit: ## # oops, still in some prior command (and what about some we already popped out of? restore them? ## # no, don't pop out yet, unless we want that...) #e start a new ToolRun of this button's command #e put it on the stack we were passed (that way its flyout gets displayed, and its pm gets displayed) #e update our flyout and PM if needed (for example, by figuring out what subcommands have been registered with this name) #e update other stuff not yet used, like a display/edit style, various filters... return pass