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.


    Drop Shadow behind ui.view()

    Pythonista
    5
    24
    17878
    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.
    • Webmaster4o
      Webmaster4o last edited by

      Can I have a drop shadow behind a ui.view? Is this what ui.set_shadow() is for? I can't find a single example of ui.set_shadow() online. Can someone give an example of a shadow behind a UI view?

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

        I figured out how to use ui.set_shadow now, but not behind a ui.view like I want.

        import ui
        
        with ui.ImageContext(100, 100) as ctx:
            ui.set_shadow('blue', 5, 5, 2)
            with ui.GState():
        	    ui.concat_ctm(ui.Transform.rotation(0.78))
        	    ui.draw_string('	Rotated text')
            ui.draw_string('Not rotated')
            ctx.get_image().show()
        

        returns

        ![result][1]

        Now how to apply this behind a view?
        [1]: http://i.imgur.com/br75lhH.jpg

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

          one way would be to create an empty View, with the bg_color you want, and add it before you add your ui element, and position it slightly down and to the right. for extra credit, use a nonzero alpha in the bg color so it is partially transparent

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

            Although this might work, I think I've found a better solution. I made a custom view class. The only problem is I can't have opacity in the view. I made a custom view class like so:

            class shadowview(ui.View):
                def draw(self):
            	    path = ui.Path.rect(0, 0, self.width-10,self.height-10)
            	    ui.set_color((0.9,0.9,0.9,1.0))
            	    ui.set_shadow("black",0,0,10)
            	    path.fill()
            

            Downsides:

            1. You need to make your UI 10 pixels wider than the widgets
            2. The view can't have opacity, or you see the shadow through and it looks funny

            If anyone can find a solution without these downsides, that'd be great. I got my UI looking like this:
            result

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

              it would be possible to auto-size based on the enclosed views, or override add_subview to force the enclosed subviews to be 10px smaller than the outer view.

              also, you could only draw the visible part of the shadow (two thin rectangles) rather than the whole thing, which would allow opacity.

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

                Thanks

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

                  How would I only draw the visible part of the shadow?

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

                    Anyone? When @JonB says

                    you could only draw the visible part of the shadow (two thin rectangles) rather than the whole thing

                    How do I accomplish this?

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

                      Webmaster4o, I am not really sure. But if you make 2 rects only the width you want to draw in ( path seems to be accumulative) Also there is a path.line_to. Sorry, only guessing and trying to give some ideas. I tried to do it in code, but I am not good enough at the moment

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

                        Ok, thanks, I'll try.

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

                          Ok, I found a solution! Here's my class:

                          class shadowview(ui.View):
                          	'''A class for a ui.View that has a shadow behind it.
                          
                          	This is accomplished by:
                          	1. Draw the background
                          	2. Redraw with a shadow, but set clipping so only the edge of the shadow
                          	shows. This prevents the part of the shadow that's under the background 
                          	from showing.
                          	
                          	'''
                          	def draw(self):
                          		
                          		'1'
                          		#Setup path of window shape
                          		path = ui.Path.rect(0, 0, self.width-10, self.height-10)
                          		
                          		#Draw background
                          		ui.set_color((0.95,0.95,0.95,0.5))
                          		path.fill()
                          		
                          
                          		'2'
                          		#Setup mask by creating image
                          		from PIL import ImageDraw
                          		i = Image.new('RGBA',(520,290), (255,255,255,0))
                          		draw = ImageDraw.Draw(i)
                          		draw.rectangle((self.width-10, 0, self.width, self.height),fill=(0,0,0,255))
                          		draw.rectangle((0, self.height-10, self.width, self.height),fill=(0,0,0,255))
                          		
                          		#Convert to UI, apply the mask, and draw shadow!
                          		i = pil_to_ui(i)
                          		i.clip_to_mask()
                          		ui.set_color((1,1,1,1))
                          		ui.set_shadow("black",-2,-2,10)
                          		path.fill()
                          

                          It fixes the problem of opacity by drawing the shadow rectangle with clipping set so only the shadow is drawn.
                          Before, I got something like this when I tried to draw with a transparent background:

                          1

                          This was because you could see the shadow behind the view.
                          Now, I have something like this:
                          1
                          Because only part of the shadow is drawn.

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

                            I have been playing around with objc_util again and came up with this piece of code you might find applicable to your code above:

                            # coding: utf-8
                            from objc_util import *
                            import ui
                            
                            UIColor = ObjCClass('UIColor')
                            
                            view = ui.View(frame=(0,0,500,500))
                            box  = ui.View(frame=(0,0,100,100))
                            
                            view.background_color = 'white'
                            box.background_color = 'red'
                            box.center = view.center
                            
                            view.add_subview(box)
                            
                            box_pntr = ObjCInstance(box)
                            ## Note: this allows for shadows to be drawn
                            box_pntr.layer().setMasksToBounds_(False)
                            box_pntr.layer().setCornerRadius_(6)
                            ## Note: CGColor is needed in order for this to work
                            box_pntr.layer().setBorderColor_(UIColor.cyanColor().CGColor())
                            box_pntr.layer().setBorderWidth_(3)
                            box_pntr.layer().setShadowRadius_(10)
                            box_pntr.layer().setShadowOffset_(CGSize(0,0))
                            box_pntr.layer().setShadowOpacity_(1.0)
                            
                            view.present('sheet')
                            

                            Hope you find this useful.

                            Phuket2 2 Replies Last reply Reply Quote 3
                            • Phuket2
                              Phuket2 @blmacbeth last edited by

                              @blmacbeth , thanks for sharing. Even though I wasn't the one asking for it, I have saved it for use later 😜

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

                                @blmacbeth, I am just playing around with ui.TableView.datat_source at the moment. But I just remembered your code for shadowing a view. I wanted to try it. I did, I cut it up just to return a ui.View. But the full script below (nothing complete about the script, still working on it. Just excited and wanted to share) . But It looks nice.
                                But it would be so much more useful if you created a custom class based on ui.View, and tweak the settings etc... This could be a very cool class to submit to the pythonistia_tools repo.
                                But thanks again. I don't play with objc stuff for now... Still trying to learn python.

                                # coding: utf-8
                                
                                import ui
                                from objc_util import *
                                
                                def shadow_box(parent):
                                	# using chopped up code from @blmacbeth
                                	UIColor = ObjCClass('UIColor')
                                
                                	f = ui.Rect(*parent.bounds).inset(10,10)
                                	box  = ui.View(frame=f)
                                
                                	box.background_color = 'white'
                                
                                	box_pntr = ObjCInstance(box)
                                	## Note: this allows for shadows to be drawn
                                	box_pntr.layer().setMasksToBounds_(False)
                                	box_pntr.layer().setCornerRadius_(6)
                                	## Note: CGColor is needed in order for this to work
                                	box_pntr.layer().setBorderColor_(UIColor.grayColor().CGColor())
                                	box_pntr.layer().setBorderWidth_(.5)
                                	box_pntr.layer().setShadowRadius_(10)
                                	box_pntr.layer().setShadowOffset_(CGSize(0,0))
                                	box_pntr.layer().setShadowOpacity_(1.0)
                                
                                	return box
                                
                                class AbstractDataSource(object):
                                	def __init__(self, tbl, items, **kwargs):
                                		# assign positional args
                                		tbl.data_source = self
                                		tbl.data_source.items = items
                                
                                		self.sections = None
                                		self.can_move = False
                                		self.can_delete = False
                                		self.can_edit = False
                                		self.make_cell_func = None
                                
                                		self.cell_type = ''
                                
                                		self.sec_title = None
                                
                                		for k,v in kwargs.iteritems():
                                			if hasattr(self, k):
                                				setattr(self, k, v)
                                
                                	def tableview_number_of_rows(self, tv, sec):
                                		# Return the number of rows in the section
                                		return len(tbl.data_source.items)
                                
                                	def tableview_cell_for_row(self, tv, sec, row):
                                		# Create and return a cell for the given section/row
                                		if self.make_cell_func:
                                			return self.make_cell_func(tv, sec, row)
                                
                                		cell = ui.TableViewCell(self.cell_type)
                                		cell.text_label.text = 'Foo Bar'
                                		return cell
                                
                                	def tableview_title_for_header(self, tv, sec):
                                		# Return a title for the given section.
                                		# If this is not implemented, no section headers will be shown.
                                		if self.sec_title:
                                			return self.sec_title
                                		elif not self.sections:
                                			return None
                                		else:
                                			return 'Some Section'
                                
                                	def tableview_can_delete(self, tv, sec, row):
                                		# Return True if the user should be able to delete the given row.
                                		return True
                                
                                	def tableview_can_move(self, tv, sec, row):
                                		# Return True if a reordering control should be shown for the given row (in editing mode).
                                		return True
                                
                                	def tableview_delete(self, tv, sec, row):
                                		# Called when the user confirms deletion of the given row.
                                		pass
                                
                                	def tableview_move_row(self, tv, from_sec, from_row, to_sec, to_row):
                                		# Called when the user moves a row with the reordering control (in editing mode).
                                		pass
                                
                                
                                class MyListDataSource(AbstractDataSource):
                                	def __init__(self, tbl, items, **kwargs):
                                		AbstractDataSource.__init__(self, tbl,  items , **kwargs)
                                		# we do this instead of overriding, to get extended functionality..
                                		self.make_cell_func = self.make_cell
                                
                                	def make_cell(self, tv, sec, row):
                                		cell = ui.TableViewCell(self.cell_type)
                                		cell.text_label.text = 'make_cell - row' + str(row)
                                		return cell
                                
                                if __name__ == '__main__':
                                	f = (0,0,500, 500)
                                	v = ui.View(frame = f, bg_color = 'white')
                                	tbl = ui.TableView()
                                	box = shadow_box(v)
                                	v.add_subview(box)
                                	ds = MyListDataSource(tbl, range(20), sec_title = 'ian πŸ‘Ώ')
                                	box.add_subview(tbl)
                                	v.present('sheet')
                                	r = ui.Rect(*tbl.superview.bounds).inset(10,10)
                                	tbl.frame = r```
                                1 Reply Last reply Reply Quote 0
                                • blmacbeth
                                  blmacbeth last edited by

                                  Ask and he shall receive. It's not perfect, yet. There are some clipping problems I can't figure out.

                                  # coding: utf-8
                                  from objc_util import *
                                  import ui
                                  
                                  UIColor = ObjCClass('UIColor')
                                  
                                  def Color(red=0, green=0, blue=0, alpha=1):
                                  	return UIColor.colorWithRed_green_blue_alpha_(red, green, blue, alpha)
                                  
                                  class ShadowView (ui.View):
                                  	def __init__(self, *args, **kwargs):
                                  		super(ShadowView, self).__init__()
                                  		self.pntr = ObjCInstance(self)
                                  		self.pntr.layer().setMasksToBounds_(False) ## Go ahead and do this.
                                  		
                                  	@property 
                                  	def corner_radius(self):
                                  		return self.pntr.layer().cornerRadius()
                                  		
                                  	@corner_radius.setter
                                  	def corner_radius(self, val):
                                  		self.pntr.layer().setCornerRadius_(val)
                                  		
                                  	@property
                                  	def border_color(self):
                                  		return self.pntr.layer().borderColor()
                                  		
                                  	@border_color.setter
                                  	def border_color(self, color):
                                  		self.pntr.layer().setBorderColor_(Color(*color).CGColor())
                                  		
                                  	@property
                                  	def border_width(self):
                                  		return self.pntr.layer().borderWidth()
                                  		
                                  	@border_width.setter
                                  	def border_width(self, val):
                                  		self.pntr.layer().setBorderWidth_(val)
                                  		
                                  	@property 
                                  	def opacity(self):
                                  		return self.pntr.layer().opacity()
                                  		
                                  	@opacity.setter
                                  	def opacity(self, val):
                                  		self.pntr.layer().setOpacity_(value)
                                  		
                                  	@property 
                                  	def hidden(swlf):
                                  		return self.pntr.layer().hidden()
                                  		
                                  	@hidden.setter
                                  	def hidden(self, val):
                                  		self.pntr.layer().setHidden_(val)
                                  		
                                  	@property 
                                  	def masks_to_bounds(self):
                                  		return self.pntr.layer().masksToBounds()
                                  		
                                  	@masks_to_bounds.setter
                                  	def masks_to_bounds(self, val):
                                  		self.pntr.layer().setMasksToBounds_(val)
                                  		
                                  	@property 
                                  	def mask(self):
                                  		return self.pntr.layer().mask()
                                  		
                                  	@mask.setter
                                  	def mask(self, new_mask):
                                  		self.pntr.layer().setMask_(new_mask)
                                  		
                                  	@property
                                  	def double_sided(self):
                                  		return self.pntr.layer().doubleSided()
                                  		
                                  	@double_sided.setter
                                  	def double_sided(self, val):
                                  		self.pntr.layer().setDoubleSided_(val)
                                  		
                                  	@property
                                  	def shadow_opacity(self):
                                  		return self.pntr.layer().shadowOpacity()
                                  		
                                  	@shadow_opacity.setter
                                  	def shadow_opacity(self, val):
                                  		self.pntr.layer().setShadowOpacity_(val)
                                  		
                                  	@property
                                  	def shadow_radius(self):
                                  		return self.pntr.layer().shadowRadius()
                                  		
                                  	@shadow_radius.setter
                                  	def shadow_radius(self, val):
                                  		self.pntr.layer().setShadowRadius_(val)
                                  		
                                  	@property
                                  	def shadow_offset(self):
                                  		return self.pntr.layer().shadowOffset()
                                  		
                                  	@shadow_offset.setter
                                  	def shadow_offset(self, offset):
                                  		## offset should be a tuple, but I'll take a CGSize
                                  		if isinstance(offset, CGSize):
                                  			self.pntr.layer().setShadowOffset_(offset)
                                  		elif isinstance(offset, tuple):
                                  			self.pntr.layer().setShadowOffset_(CGSize(*offset))
                                  		else:
                                  			raise TypeError("Cannot use type %s. Use CGSize or tuple" % type(offset))
                                  			
                                  	@property
                                  	def shadow_color(self):
                                  		return self.pntr.layer().shadowColor()
                                  		
                                  	@shadow_color.setter
                                  	def shadow_color(self, color):
                                  		if isinstance(color, UIColor.CGColor()):
                                  			self.pntr.layer().setShadowColor_(color)
                                  		elif isinstance(color, tuple) and len(color) == 4:
                                  			self.pntr.layer().setShadowColor_(Color(*color).CGColor())
                                  		else:
                                  			raise ValueError('Cannot use type %s. Use UIColor or tuple' % type(color))
                                  			
                                  		@property
                                  		def shadow_path(self):
                                  			return self.pntr.layer().shadowPath()
                                  			
                                  		@shadow_path.setter
                                  		def shadow_path(self, path):
                                  			self.pntr.layer().setShadowPath_(path)
                                  			
                                  		@property
                                  		def style(self):
                                  			return self.pntr.layer().style()
                                  			
                                  		@style.setter
                                  		def style(self, style):
                                  			self.pntr.layer().setStyle_(style)
                                  			
                                  if __name__ == '__main__':	
                                  	view = ui.View(frame=(0,0,500,500))
                                  	box  = ShadowView(frame=(0,0,100,100))
                                  	
                                  	view.background_color = 'white'
                                  	box.background_color = 'red'
                                  	box.center = view.center
                                  	
                                  	view.add_subview(box)
                                  	
                                  	box.masks_to_bounds = False
                                  	box.corner_radius = 6.
                                  	box.border_color = (0,1,0)
                                  	box.border_width = 6
                                  	box.shadow_radius = 10
                                  	box.shadow_offset = (0,0)
                                  	box.shadow_opacity = 1
                                  	
                                  	view.present('sheet')
                                  

                                  I tested most of it, but there may still be some funny-ness.

                                  B.

                                  Phuket2 1 Reply Last reply Reply Quote 2
                                  • Phuket2
                                    Phuket2 @blmacbeth last edited by

                                    @blmacbeth , ok thanks. I will try it. Maybe I am wrong, but doesn't your call to super need to be like
                                    super(ShadowView, self).init(self, *args, **kwargs)

                                    If not, then I would love to know why not.

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

                                      @Phuket2 Since you've already passed self as an argument to super, you already get (bound) methods instead of normal functions, so you don't need to pass self as the first parameter to __init__. Though you are right, the *args and **kwargs do need to be passed (if you want ui.View to interpret them, which I assume is the case). For ui.View the docs say that you don't need to call super().__init__() at all, perhaps everything important is already handled by __new__.

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

                                        @Phuket2 Ha! You caught me. That may be a problem… it should look like:

                                        super(ShadowView, self).__init__(*args, **kwargs)
                                        

                                        I must have been in a zone to miss that. There is no need to repeat self in the __init__ call because super(ShadowView, self) returns an instance of the superclass, which will pass self implicitly.

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

                                          @dgelessus , thanks. I tend not to call super anymore. I normally call the class init method. I think I did that because I was doing multiple inheritance and was having a hard time following what was going on 😭, to this day I still don't know the most correct way.
                                          I am pretty sure I have checked this theory of not having to pass on params to ui.View, I know it's in the docs. As far as I can see it does not work. Maybe if you have no init. Not sure that should cancel new, but maybe it's been written to work that way. So many combinations, I get confused 😱

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

                                            @blmacbeth , lol. I understand. I thought you had some special trick up your sleeve 😝

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