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
    28188
    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.
    • 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
                                • abcabc
                                  abcabc last edited by

                                  I have changed two lines in your program to make it work. See the following gist

                                  https://gist.github.com/5e7b8beb9a0b35aaafbcd06b0cd30340

                                  line 81:
                                  self.degree = 90

                                  line 183
                                  self.degree = sender.value *360 -90

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

                                    Sorry. Typo. line 103 not 183.

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

                                      @abcabc , thanks. It makes it work. I was not so worried about that. More worried bout you need to know the math inside to get it to work. The reason I tried -90, was because often from what I can see anyway , the geometry done in Pythonista is based on radians. But the point is does not matter what math you use, but as a consumer of your class, the less I have to know the better. Just because you know the math back the front, nothing to say I should have to know it. But something like degrees is pretty understandable for most people. Again, look it's your code and effort. I am just pushing in the case you want to make consumable class that the majority could use with ease. It's like cooking (which I love) you want as many people as possible to try your food. That's what makes the effort worth while and gives you a buzz when you get it right. I think writing classes/functions here is the same thing. Well, for me it's like that.

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

                                        @abcabc , a small update to the gist. Its not suppose to present an ideal case. But combining the views/funcs can start to get something that looks ok. Have numbers around the outer ring in this example, would look more realistic with major and minor tick marks for example. But for me your circular_slider makes this possible.

                                        Edit: of course the outter ring not even needed

                                        1 Reply Last reply Reply Quote 0
                                        • First post
                                          Last post
                                        Powered by NodeBB Forums | Contributors