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
-
-
@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
-
Yes. You are right. I will update the code as suggested by you. Thanks for the suggestion.
-
@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.
-
@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
-
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.
-
@Phuket2 There could be some problem with centering. I will look into that. Anyway I have to do some more testing.
-
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)
-
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. -
@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')
-
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
-
@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 😬
-
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 -
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')
-
@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
-
@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 😱😂
-
@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/ -
@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 😬😬😬 -
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).
-
@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