Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
[Lab] Button Bar, for testing
-
I often find that when trying to make a ui, I want to call parts of the code that eventually will be called by an interface interaction. When when building up the view and testing as I go along I don't have everything in place, so I end up creating dummy buttons or menuitems to trigger these calls so I can view the results and make sure it's working as I expect.
That's why I started on this view, below. It's far from being perfect. Just an idea, that I am also skeptical about. Maybe @JonB overlay class a better way to implement this. I tried, I just had a hard time controlling it, as it wasn't designed for this purpose specifically. Anyway, the below is an attempt. Needs to be tightened up, but it works. Just feels messy and too long.# Pythonista Forum - @Phuket2 import ui class DebugToolBar(ui.View): ''' DebugToolBar description: A helper view (debugging), when you need to fire some functions etc. in your interface when testing a view. its not trying to be nice and fit in. it adds its self to the parent view and tries to bring itself to the front. params: parent = target ui.View parent h = height of the view num_btns = the number of buttons to create ''' def __init__(self, parent, h=44, num_btns=4, *args, **kwargs): super().__init__(*args, **kwargs) self.parent = parent self.num_btns = num_btns self.name = 'bv' self.width = (h * num_btns) self.height = h self.bg_color = 'cornflowerblue' self.alpha = .5 self.corner_radius = 4 self.x = (parent.width / 2) - (self.width / 2) self.make_view() self.bring_to_front() # position the view and set the flex attr # this view should be both size and orientation friendly. # the view is at the bottom of the parent self.y = parent.height - self.height self.flex = 'lrtb' # add the view to the parent. naughty! parent.add_subview(self) def make_view(self): r = ui.Rect(*self.bounds).inset(2, 2) h = r.height btn_rect = ui.Rect(0, 0, h, h) btn_rect.y = r.min_y offset = (r.width/2) - ((self.num_btns * h) / 2) for i in range(0, self.num_btns): btn_rect.x = (h * i) + offset btn = self._make_btn(str(i), btn_rect) btn.action = self.btn_action self.add_subview(btn) def _make_btn(self, name, rect): r = ui.Rect(*rect).inset(4, 4) btn = ui.Button(name=name, frame=r) btn.corner_radius = r.width / 2 btn.title = name btn.bg_color = 'white' btn.tint_color = 'black' btn.callback = None # dynamically created attr btn.font = ('Arial Rounded MT Bold', btn.width * .4) return btn def btn_action(self, sender): # self pop_effect not done to be smart, rather to give some real # user feedback that the btn was pressed. # because i am always sitting in a bar with live band or music # videos playing, i dont think about sound feedback. Really!! self.pop_effect(v=sender) # if set_action method is used, we save the callback func in # a dynamically created attr 'callback' if sender.callback: sender.callback(sender) return if hasattr(self.parent, 'bv_action'): self.parent.bv_action(sender) else: print('''Create a method in the class '{}' like - '{}' , if you want your parent to be called back with a btn presses''' .format(self.parent.__class__.__name__,\ 'def bv_action(self, sender)')) def set_action(self, btn_index, func): ''' attach a function to one of the buttons, well we fake it. we dont attach the func directly. we add it to a dynamically created attr obj.callback Then the code flow remains the same, we still animate etc. ''' if btn_index is -1: for btn in self.subviews: btn.callback = func else: try: btn = self.subviews[btn_index] btn.callback = func except: raise def pop_effect(self, v, duration=.4, scale=1.25): ''' pop - just used to pop a button in this case by scaling it. aka. a visual effect applied to v ''' x = y = scale def a(): v.transform = ui.Transform() v.transform = ui.Transform.scale(x, y) ui.animate(a, duration=duration) class MyClass(ui.View): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.make_view() # best to call this last to make sure we end up on top # or you could do it manually with bv.bring_to_front() bv = DebugToolBar(self, h=60, num_btns=6) def make_view(self): # make the view as per normal pass # DebugToolBar, will call this method if it exists, # and if set_action has not been called. def bv_action(self, sender): # to show how this callback could be used, silly example if sender.name == '0': # do something self.bg_color = 'red' elif sender.name == '1': # do something self.bg_color = 'green' elif sender.name == '2': # do something self.bg_color = 'blue' elif sender.name == '3': # do something self.bg_color = 'aqua' elif sender.name == '4': # do something self.bg_color = 'orange' elif sender.name == '5': # do something self.bg_color = 'maroon' def func_action(sender): # example of a func that we set with set_action sender.superview.parent.bg_color = 'purple' if __name__ == '__main__': _use_theme = False w, h = 375, 667 f = (0, 0, w, h) style = 'sheet' mc = MyClass(frame=f, bg_color='white') mc.present(style=style, animated=False) # another way to link a func to a button by btn index # if -1 is passed, all buttons are assigned the callback func # a more compact way, trying to reduce interference mc['bv'].set_action(1, func_action)
-
Hmmm, had a brainwave 😱 Will implement as a popover instead.
-
def bv_action(self, sender): # to show how this callback could be used, silly example bg_colors = 'red green blue aqua orange maroon'.split() self.bg_color = bg_colors[int(sender.name)]
-
@ccc , yeah, it would have been, but .just trying to be quite literal. Sometimes condensing things down does not give you the authors percieved usage. I know I have troubles at times, trying to figure out exactly what the intended usage is. I am sure this just comes with practice. Anyway, my point being there are times when I could significantly reduce some code, but choose not to in the hope it makes it clearer to what I had in mind. Well, that's the rationale anyway 😬🎉
-
@ccc , but still what you keep saying works. I don't think to use sum(), I just add stuff 😬
But your comments were fresh and echoing in my head when I went to write the below basic function. The fact that I though I needed to write a function was probably the tell tale sign of going on the wrong track.
It seems so simple, but you have to be thinking about it....I am sure I have done the below as a loop many times before. Tend to forget how well iteriables are supported throughout Python.def height_of_subviews(parent): return sum(v.height for v in parent.subviews)
-
For me, a three line function is almost always easier to write, read, understand, and debug than a thirteen line function. It will probably execute faster too. The more code, the more surface area for bugs appear. The more code, the more symbols that the reader needs to interpret to understand execution outcomes.
I am not trying to write obfuscated code. I want to be understood both by the computer and by the reader. I want to be concise because that usually helps both execution time and human comprehension of the intent of the code. As I am writing, I like to ask the question,"If I came across this code 6 months from now could I quickly understand the intent and any side effects?". If not, then I rewrite.
Well, that's the rationale anyway 😬🎉
-
@ccc , yes I get you about a 3 line function, but to change the colors was just to show something happened. Was not the practical use in this case. The idea each button would be performing a totally different function.
But look it does not really matter. I get you. You are a disciplined programmer. You cant easily turn that on and off; you see it one way, the right way as you perceive it. I was the same way when coding was my profession. It also served me well. You have to be like that or at least you should be in my view. But I am back to amateur status with not much chance of getting beyond that unless I give up the BL 😬 -
@Phuket2 thanks a lot for sharing this, helps a ton.