omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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.


    [Share] a list of rects distributed around 360 degrees

    Pythonista
    circle rects share 360
    5
    44
    28111
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • cvp
      cvp last edited by

      @abcabc you're right but this code was only valuable for 12 items, not for 8, for instance in the code for N,....,E,.....,S,....W,..., or for any range

      1 Reply Last reply Reply Quote 0
      • abcabc
        abcabc last edited by

        Yes. You are right. I will update the code as suggested by you. Thanks for the suggestion.

        1 Reply Last reply Reply Quote 0
        • Phuket2
          Phuket2 @abcabc last edited by

          @abcabc , @cvp , _range_12 is ok. Just a list of 12 numbers between 0.0 and 1.0. But purposely not done linearly. That's why I put values into the list. To do it without constants you need some math. You can't start a zero or even .1 or even .2 for that matter, the resulting alpha is too faint. Also depending on what you want to do, the progression normally will look better if its not linear.

          1 Reply Last reply Reply Quote 0
          • Phuket2
            Phuket2 @abcabc last edited by Phuket2

            @abcabc , ran your code. Works well. But you are not centered in the view. Maybe there is a param I can't see. But on my iPad Pro 12 inch I get the below.

            Edit: your x,y are off. Too much added somewhere

            1 Reply Last reply Reply Quote 0
            • Phuket2
              Phuket2 last edited by

              Sorry a little out of sync with some of my comments. I am not sure it's just me or not, but I have had a hard time replying the last 40 mins or so.

              1 Reply Last reply Reply Quote 0
              • abcabc
                abcabc last edited by

                @Phuket2 There could be some problem with centering. I will look into that. Anyway I have to do some more testing.

                1 Reply Last reply Reply Quote 0
                • Phuket2
                  Phuket2 last edited by

                  Below is a pretty nice adaptation of using the shared function here, after it's been fixed up 😱

                  But for use in this function or not, the get_rotated_icon function is nice or let's say functional. Simple function, code comes from help from omz on a similar subject. Probably not for 60fps stuff, but I think good for ui stuff.

                  import editor
                  import math
                  import ui
                  
                  # this is a pretty funky function...
                  def get_rotated_icon(named_icon_name, wh = 32, degree = 0):
                  	'''
                  		help from @omz
                  		https://forum.omz-software.com/topic/3180/understanding-ui-transform-rotation
                  	'''
                  	r = ui.Rect(0, 0, wh, wh)
                  	img = ui.Image.named(named_icon_name)
                  	with ui.ImageContext(wh, wh) as ctx:
                  		ui.concat_ctm(ui.Transform.translation(*r.center()))
                  		ui.concat_ctm(ui.Transform.rotation(math.radians(degree)))
                  		ui.concat_ctm(ui.Transform.translation(*r.center() * -1))
                  		img.draw()
                  		return ctx.get_image()
                  		
                  def make_button(idx, title, name = None):
                  	def button_action(sender):
                  		print('Button {} was pressed.'.format(sender.name))
                  		
                  	#btn = ui.Button(title=calendar.month_abbr[i+1])
                  	name = name if name else title
                  	btn = ui.Button(name = name, title=title )
                  	btn.action = button_action
                  	#btn.alpha = _range_12[idx]
                  	btn.border_width = .5
                  	btn.bg_color = 'white'
                  	btn.text_color = btn.tint_color = 'black'
                  	return btn
                  	
                  def css_clr_to_rgba(css_name, a):
                  	c = ui.parse_color(css_name)
                  	return (c[0], c[1], c[2], a)	
                  	
                  def rects_on_circle_path(rect_path, obj_width, margin=2, num_objs=12):
                  	def calculate_a_rect(i):
                  		a = 2 * math.pi * i/num_objs - math.pi/2
                  		# careful: cos,sin! not sin,cos
                  		pos = (math.cos(a)*(radius*1), math.sin(a)*(radius*1)) 
                  		r1 = ui.Rect(*pos, obj_width, obj_width)
                  		r1.x += r.width / 2 - obj_width / 2 + r.x
                  		r1.y += r.height / 2 - obj_width / 2 + r.y
                  		
                  		return r1
                  		
                  	r = ui.Rect(*rect_path).inset(obj_width / 2 + margin,
                  	obj_width / 2 + margin)
                  	radius = r.width / 2
                  	return r, [calculate_a_rect(i) for i in range(num_objs)]
                  
                  class MyClass(ui.View):
                  	# some ideas
                  	_list=['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW' ]
                  	
                  	def __init__(self, *args, **kwargs):
                  		super().__init__(*args, **kwargs)
                  		self.cir_rect = None
                  		self.obj_list = []
                  		self.mid_btn = None
                  		
                  		self.make_view()
                  		
                  	def make_view(self):
                  		for i in range(len(self._list)):
                  			obj = make_button(i, title=self._list[i])
                  			obj.image = get_rotated_icon('iob:arrow_up_a_256', wh = 256, degree = i * 45)
                  			self.obj_list.append(obj)
                  			self.add_subview(obj)
                  		
                  		btn = make_button(i, title='C')
                  		self.mid_btn = btn
                  		self.add_subview(btn)
                  		
                  			
                  	def layout(self):
                  		r, rects = rects_on_circle_path(self.bounds, obj_width=70, 								margin=20, num_objs=len(self._list))
                  		self.cir_rect = r
                  		for i, btn in enumerate(self.obj_list):
                  			btn.frame = rects[i]
                  			btn.title = ''
                  			btn.corner_radius = btn.width / 2
                  		
                  		btn = self.mid_btn
                  		btn.center = r.center()
                  		btn.corner_radius = btn.width / 2
                  		
                  	def draw(self):
                  		# just to see the path when testing...
                  		s = ui.Path.oval(*self.cir_rect)
                  		with ui.GState():
                  			ui.set_color(css_clr_to_rgba('lime', .4))
                  			s.line_width = 1
                  			s.stroke()
                  	
                  	
                  if __name__ == '__main__':
                  	_use_theme = True
                  	w=h = 600
                  	f = (0, 0, w, h)
                  	
                  	mc = MyClass(frame=f, bg_color='white')
                  	
                  	if not _use_theme:
                  		mc.present('sheet', animated=False)
                  	else:
                  		editor.present_themed(mc, theme_name='Oceanic', style='sheet', animated=False)
                  				
                  

                  1 Reply Last reply Reply Quote 0
                  • JonB
                    JonB last edited by

                    FWIW,
                    https://gist.github.com/6fc02b7d75eb22111b826cfdb2394697
                    is an example using ui.Transform to simulate your last imagE. Probably not as useful, but compact in this case.

                    Phuket2 1 Reply Last reply Reply Quote 0
                    • abcabc
                      abcabc last edited by

                      @JonB very nice. I have changed your code slightly to do a "semi-circular layout" which seems to be used in some of the ios applications.

                      import ui, calendar
                      from math import pi, sin, radians
                      
                      def make_button(i, v, N):
                          def button_action(sender):
                              print('Button {} was pressed.'.format(sender.title))
                      
                          btn = ui.Button(title=calendar.month_abbr[i+1])
                          btn.action = button_action
                          btn.height=btn.width=64
                          btn.alpha = sin(radians(15.+75.0/(N-1)*i))
                          btn.border_width = .5
                          btn.corner_radius = btn.width *.5
                          btn.bg_color = 'orange'
                          btn.text_color = btn.tint_color = 'black'
                          center_x, center_y = v.bounds.center()
                          btn.center = (btn.width/2+5.0, center_y) #v.bounds.center()
                          btn.transform=ui.Transform.translation(0,-(v.height/2-btn.height/2)
                                              ).concat(ui.Transform.rotation(2.*pi*i/N))
                          return btn
                          
                      v=ui.View(frame=(0,0,576,576))
                      v.bg_color=(1,1,1)
                      N = 12
                      N1 = 7
                      for i in range(0,N1):
                          v.add_subview(make_button(i, v, N))
                      v.present('sheet')
                      
                      1 Reply Last reply Reply Quote 0
                      • Phuket2
                        Phuket2 last edited by

                        Nice guys. As painfully as obvious as it is I have never thought of rotating the icons. After I did my compass test, I thought I would try it with arrows, could be used as a type of controller etc. then I was looking for the arrows offset by 45 degrees. Then it was like a big steel ball dropped from a great height on to my head. But also handy as you are only dealing with one image_name. I don't think speed would be ever a problem. Also simple to write out to PNG files if required. Anyway, I find it amazing how many things are sitting right under my nose I don't see

                        1 Reply Last reply Reply Quote 0
                        • Phuket2
                          Phuket2 @JonB last edited by

                          @JonB , yeah your approach a lot more straight fwd. to be honest I didn't think about rotating the button. But I was also thinking about drawing into the view vrs using buttons. But in this case, no reason not just to use the buttons. Many ways to skin the cat. But I will hold on to your code also, has its own versatility 😬

                          1 Reply Last reply Reply Quote 0
                          • JonB
                            JonB last edited by

                            For what its worth, ui.concat_ctm along lets you use ui.Transforms inside ImageContexts.
                            This is perhaps a convolouted example, showing how you can use transforms rather than trying to do math for this sort of thing (in this case the sort of draggable lollipop hour selector). In the end, a little math is needed for the touch handling.
                            https://gist.github.com/84489b13a17cdd46288f16b50b2f7bc3

                            Phuket2 1 Reply Last reply Reply Quote 0
                            • abcabc
                              abcabc last edited by

                              Thanks @JonB for sharing this code. FWIW, I have modified this code to make a circular slider.

                              import ui
                              from math import pi,atan2
                              
                              class CircularSlider(ui.View):
                                  def __init__(self,*args,**kwargs):
                                      ui.View.__init__(self,*args,**kwargs)
                                      self.a = 0
                                      self.value = (self.a+pi)/(2*pi)
                                      
                                  def draw(self):
                                      scl=min(self.width,self.height)
                                      self.scl=scl
                                      btn_siz=min(22/scl,0.05)
                                      #work in normalized units
                                      ui.concat_ctm(ui.Transform.scale(scl,scl))
                                      #origin at center
                                      ui.concat_ctm(ui.Transform.translation(.5,.5))
                                      ui.set_color('#1aa1b5')
                                      o = ui.Path.oval(-.5+btn_siz, -.5+btn_siz, 1-2*btn_siz, 1-2*btn_siz)
                                      o.line_width=2/scl
                                      o.stroke()
                                      #rotate by angle
                                      ui.concat_ctm(ui.Transform.rotation(self.a))
                                      # center origin at button
                                      ui.concat_ctm(ui.Transform.translation(.5-btn_siz,0))
                                      #optional: to keep images upright
                                      #ui.concat_ctm(ui.Transform.rotation(-self.a))
                                      p=ui.Path.oval(-btn_siz,-btn_siz,2*btn_siz,2*btn_siz)
                                      p.fill()
                              
                                  def touch_moved(self,touch):
                                      dp=touch.location-touch.prev_location
                                      self.a=atan2(touch.location.y-self.scl/2.,touch.location.x-self.scl/2.)
                                      self.value = (self.a+pi)/(2*pi)
                                      self.set_needs_display()
                                      
                                  def touch_ended(self, touch):
                                      print(self.value)
                              
                              d = CircularSlider(frame=(0,0,500, 500),bg_color='white')
                              d.present('sheet')
                              
                              Phuket2 1 Reply Last reply Reply Quote 0
                              • Phuket2
                                Phuket2 @abcabc last edited by

                                @abcabc , that's really nice. Would be great if you made your class a utility class. I mean passing all Params etc. to the class to be able to personalize it and make it a bit more generic. Eg, if you could set an image and set its rotation center, you could make a volume dial control. Maybe not the best explanation, but I think you see what I mean. But this sort of class in a generic form would be very useful too many

                                1 Reply Last reply Reply Quote 0
                                • Phuket2
                                  Phuket2 @JonB last edited by

                                  @JonB , I am pretty sure there is a good reason for it, but the reason eludes me. I am thinking about ui Elements having rotate, scale, axis etc attrs built in. I do understand that the Transform.xxx can be chained and used in animations etc. But it seems to me that any given ui object should have attrs such as rotate, scale, translate etc...maybe I am missing something about the math, but I don't think so. Just seems like a lot of hoops to go through when it could just as easily be a attr on any ui Element. Eg ui.Button(rotate_deg = 5) etc... Seems reasonable, well more than reasonable to me.

                                  @omz not sure what you think about this. I think it would be really helpful. Not everyone that use Python/Pythonista are going to be geometrically blessed. I can only imagine some things are not that easy to implement given it all has to fit into XCode template also. But still it seems to me if it was possible, would make ui things easier for people like me 😱😂

                                  1 Reply Last reply Reply Quote 0
                                  • abcabc
                                    abcabc last edited by

                                    @Phuket2 I have modified the circular slider code so that you can use it like ui slider. The code and examples are are available in the following repository.

                                    https://github.com/balachandrana/pythonista_circular_slider

                                    In test1.py and test2.py you can control the ui slider with circular slider and vice versa. The test1.py code uses continuous mode and
                                    in test2.py tint_color is set to red and continuous mode sets to false. In test3.py you can change the center image by slider.

                                    Knob like designs (see the examples below) would require more effort and better designer skills. Anyway I will try to do some simple things later.
                                    http://www.hongkiat.com/blog/beautiful-volume-dials-knobs/

                                    1 Reply Last reply Reply Quote 0
                                    • Phuket2
                                      Phuket2 last edited by

                                      @abcabc , thanks. I didn't really get the self.a property though. I was thinking if it set that to -90 it would basically set things to 0 on the clock. However it's set at 270 degrees. I tried a few values, but stopped because it was clear I didn't understand it. Btw, i did not download your test .py and pyui files yet. Will get later. But anyway, I did use the action/continuous attrs. Works really well. The results from the self.value appear to be spot on to what I would expect. But I still think a few attrs a little less mathy would be nice 😁 Starting point for example. I am sure it's there, but I think it's ok to assume the users of your class are totally dumb to any math you be using internally in your class. Not to say, you need to block access to attrs that could be set directly by people who,understand the math. I just say this, because a class like this with a nice/easy API would allow people like me to make animated interface items that would otherwise would be hard to make.
                                      Btw, I love the link to the knobs and dials. But I disagree with you a little. If your class just handled the movements/tracking/queries it would be very flexible to be able create the knobs/dials in that link. Some of those fancy dials could be made by just manipulating an array of pics in a container like a ui.Button/ui.Image etc. meaning the effects don't have to real time to get a nice result , but they could also be real time effects if fast enough. I guess what I am saying your class does not need to draw anything and probably shouldn't unless for debugging help. Rather, you could overlay your class transparently on other ui objects and control the underlying objects from from class.
                                      I hope you don't mind my me giving my opinion. I say it because I would love something like this. Of course I think about other functionality, like being able to supply a list of degrees as the only stop points etc. if I could write it, I would offer some code. One thing to mention, you also use inheritance. Have your base class and create different types of user controllers/Guestures whatever to call them off the base. Could keep it a bit cleaner as your ideas exapand.
                                      Ok, that's my 5 cents worth of feedback 😬😬😬

                                      1 Reply Last reply Reply Quote 0
                                      • abcabc
                                        abcabc last edited by

                                        Please look at the tests. You need to use self.value (and not self.a which is internal' it varies from -pi tp +pi returned by atan2 function).

                                        Phuket2 1 Reply Last reply Reply Quote 0
                                        • Phuket2
                                          Phuket2 @abcabc last edited by

                                          @abcabc , ok, I will do. But still I prefer not to have to know about atan etc... Just saying...

                                          I just did this, it's bit of a mess, but just to illustrate what I was saying in my previous post. You circular_slider is just visible, but when using some graphics it would not be, just there as an overlay/controller. Again, sorry, I does nothing other than combine a number of things together. Also would hope to have multiple instances of your circular_slider on the view. For instances when you have a inner and outer control points

                                          1 Reply Last reply Reply Quote 0
                                          • Phuket2
                                            Phuket2 last edited by

                                            Sorry the last gist in the layout should have been. Not a big deal, just trying to show something.

                                            def layout(self):
                                            		r , rects = rects_on_circle_path(self.bounds, self.obj_w, margin=self.margin, N=len(self.obj_list))
                                            		self.r = r
                                            		
                                            		if not self.cs:
                                            			print('in here')
                                            			self.cs = CS.CircularSlider(frame = r, name = 'CS')
                                            			self.cs.action = self.cs_action
                                            			self.cs.continuous = True
                                            			self.cs.alpha = .05
                                            			self.add_subview(self.cs)
                                            		
                                            		for i, r in enumerate(rects):
                                            			self.obj_list[i].frame = r
                                            
                                            1 Reply Last reply Reply Quote 0
                                            • First post
                                              Last post
                                            Powered by NodeBB Forums | Contributors