Skip to content

Adding --depth option to border command to recursively border subviews #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 6, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 67 additions & 10 deletions commands/FBDisplayCommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,71 @@ def lldbcommands():


class FBDrawBorderCommand(fb.FBCommand):
colors = [
"black",
"gray",
"red",
"green",
"blue",
"cyan",
"yellow",
"magenta",
"orange",
"purple",
"brown",
]

def name(self):
return 'border'

def description(self):
return 'Draws a border around <viewOrLayer>. Color and width can be optionally provided.'
return 'Draws a border around <viewOrLayer>. Color and width can be optionally provided. Additionally depth can be provided in order to recursively border subviews.'

def args(self):
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to border. NSViews must be layer-backed.') ]

def options(self):
return [
fb.FBCommandArgument(short='-c', long='--color', arg='color', type='string', default='red', help='A color name such as \'red\', \'green\', \'magenta\', etc.'),
fb.FBCommandArgument(short='-w', long='--width', arg='width', type='CGFloat', default=2.0, help='Desired width of border.')
fb.FBCommandArgument(short='-w', long='--width', arg='width', type='CGFloat', default=2.0, help='Desired width of border.'),
fb.FBCommandArgument(short='-d', long='--depth', arg='depth', type='int', default=0, help='Number of levels of subviews to border. Each level gets a different color beginning with the provided or default color'),
]

def run(self, args, options):
colorClassName = 'UIColor'
isMac = runtimeHelpers.isMacintoshArch()
def setBorder(layer, width, color, colorClass):
lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, width))
lldb.debugger.HandleCommand('expr (void)[%s setBorderColor:(CGColorRef)[(id)[%s %sColor] CGColor]]' % (layer, colorClass, color))

obj = args[0]
depth = int(options.depth)
isMac = runtimeHelpers.isMacintoshArch()
color = options.color
assert color in self.colors, "Color must be one of the following: {}".format(" ".join(self.colors))
colorClassName = 'UIColor'
if isMac:
colorClassName = 'NSColor'

layer = viewHelpers.convertToLayer(args[0])
lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, options.width))
lldb.debugger.HandleCommand('expr (void)[%s setBorderColor:(CGColorRef)[(id)[%s %sColor] CGColor]]' % (layer, colorClassName, options.color))
if viewHelpers.isView(obj):
prevLevel = 0
for view, level in viewHelpers.subviewsOfView(obj):
if level > depth:
break
if prevLevel != level:
color = self.nextColorAfterColor(color)
prevLevel = level
layer = viewHelpers.convertToLayer(view)
setBorder(layer, options.width, color, colorClassName)
else:
# `obj` is not a view, make sure recursive bordering is not requested
assert depth <= 0, "Recursive bordering is only supported for UIViews or NSViews"
layer = viewHelpers.convertToLayer(obj)
setBorder(layer, options.width, color, colorClassName)

lldb.debugger.HandleCommand('caflush')

def nextColorAfterColor(self, color):
assert color in self.colors, "{} is not a supported color".format(color)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assert shouldn't be necessary, since it's already checked earlier in the run method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method shouldn't rely on being called from run, so it needs to protect itself by asserting as well.

return self.colors[(self.colors.index(color)+1) % len(self.colors)]

class FBRemoveBorderCommand(fb.FBCommand):
def name(self):
Expand All @@ -63,14 +101,33 @@ def name(self):
def description(self):
return 'Removes border around <viewOrLayer>.'

def options(self):
return [
fb.FBCommandArgument(short='-d', long='--depth', arg='depth', type='int', default=0, help='Number of levels of subviews to unborder.')
]

def args(self):
return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to unborder.') ]

def run(self, args, options):
layer = viewHelpers.convertToLayer(args[0])
lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, 0))
lldb.debugger.HandleCommand('caflush')
def setUnborder(layer):
lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, 0))

obj = args[0]
depth = int(options.depth)
if viewHelpers.isView(obj):
for view, level in viewHelpers.subviewsOfView(obj):
if level > depth:
break
layer = viewHelpers.convertToLayer(view)
setUnborder(layer)
else:
# `obj` is not a view, make sure recursive unbordering is not requested
assert depth <= 0, "Recursive unbordering is only supported for UIViews or NSViews"
layer = viewHelpers.convertToLayer(obj)
setUnborder(layer)

lldb.debugger.HandleCommand('caflush')

class FBMaskViewCommand(fb.FBCommand):
def name(self):
Expand Down
24 changes: 24 additions & 0 deletions fblldbviewhelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import lldb

import fblldbbase as fb
import fblldbobjcruntimehelpers as runtimeHelpers

def flushCoreAnimationTransaction():
lldb.debugger.HandleCommand('expr (void)[CATransaction flush]')
Expand Down Expand Up @@ -55,6 +56,29 @@ def convertToLayer(viewOrLayer):
else:
raise Exception('Argument must be a CALayer, UIView, or NSView.')

def isUIView(obj):
return not runtimeHelpers.isMacintoshArch() and fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % obj)

def isNSView(obj):
return runtimeHelpers.isMacintoshArch() and fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % obj)

def isView(obj):
return isUIView(obj) or isNSView(obj)

# Generates a BFS of the views tree starting at the given view as root.
# Yields a tuple of the current view in the tree and its level (view, level)
def subviewsOfView(view):
views = [(view, 0)]
yield views[0]
while views:
(view, level) = views.pop(0)
subviews = fb.evaluateExpression('(id)[%s subviews]' % view)
subviewsCount = int(fb.evaluateExpression('(int)[(id)%s count]' % subviews))
for i in xrange(subviewsCount):
subview = fb.evaluateExpression('(id)[%s objectAtIndex:%i]' % (subviews, i))
views.append((subview, level+1))
yield (subview, level+1)

def upwardsRecursiveDescription(view, maxDepth=0):
if not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % view) and not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % view):
return None
Expand Down