mirror of
https://github.com/kepler155c/opus
synced 2025-01-23 21:56:53 +00:00
drop menus
This commit is contained in:
parent
fc69d4be83
commit
91a05c07dd
274
sys/apis/ui.lua
274
sys/apis/ui.lua
@ -101,7 +101,7 @@ function Manager:init(args)
|
|||||||
elseif self.currentPage then
|
elseif self.currentPage then
|
||||||
if not self.currentPage.parent.device.side then
|
if not self.currentPage.parent.device.side then
|
||||||
local event = self:pointToChild(self.target, x, y)
|
local event = self:pointToChild(self.target, x, y)
|
||||||
if event.element.focus then
|
if event.element.focus and not event.element.inactive then
|
||||||
self.currentPage:setFocus(event.element)
|
self.currentPage:setFocus(event.element)
|
||||||
self.currentPage:sync()
|
self.currentPage:sync()
|
||||||
end
|
end
|
||||||
@ -282,7 +282,7 @@ function Manager:pointToChild(parent, x, y)
|
|||||||
y = y + parent.offy - parent.y + 1
|
y = y + parent.offy - parent.y + 1
|
||||||
if parent.children then
|
if parent.children then
|
||||||
for _,child in pairs(parent.children) do
|
for _,child in pairs(parent.children) do
|
||||||
if child.enabled and
|
if child.enabled and not child.inactive and
|
||||||
x >= child.x and x < child.x + child.width and
|
x >= child.x and x < child.x + child.width and
|
||||||
y >= child.y and y < child.y + child.height then
|
y >= child.y and y < child.y + child.height then
|
||||||
local c = self:pointToChild(child, x, y)
|
local c = self:pointToChild(child, x, y)
|
||||||
@ -653,8 +653,6 @@ function UI.Window:setTextScale(textScale)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function UI.Window:clear(bg, ...)
|
function UI.Window:clear(bg, ...)
|
||||||
debug(bg)
|
|
||||||
debug({...})
|
|
||||||
if self.canvas then
|
if self.canvas then
|
||||||
self.canvas:clear(bg or self.backgroundColor)
|
self.canvas:clear(bg or self.backgroundColor)
|
||||||
else
|
else
|
||||||
@ -808,7 +806,7 @@ function UI.Window:getFocusables()
|
|||||||
|
|
||||||
local function getFocusable(parent, x, y)
|
local function getFocusable(parent, x, y)
|
||||||
for _,child in Util.spairs(parent.children, focusSort) do
|
for _,child in Util.spairs(parent.children, focusSort) do
|
||||||
if child.enabled and child.focus then
|
if child.enabled and child.focus and not child.inactive then
|
||||||
table.insert(focusable, child)
|
table.insert(focusable, child)
|
||||||
end
|
end
|
||||||
if child.children then
|
if child.children then
|
||||||
@ -853,6 +851,22 @@ function UI.Window:scrollIntoView()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function UI.Window:getCanvas()
|
||||||
|
local el = self
|
||||||
|
repeat
|
||||||
|
if el.canvas then
|
||||||
|
return el.canvas
|
||||||
|
end
|
||||||
|
el = el.parent
|
||||||
|
until not el
|
||||||
|
end
|
||||||
|
|
||||||
|
function UI.Window:addLayer(bg, fg)
|
||||||
|
local canvas = self:getCanvas()
|
||||||
|
|
||||||
|
return canvas:addLayer(self, bg, fg)
|
||||||
|
end
|
||||||
|
|
||||||
function UI.Window:addTransition(effect, args)
|
function UI.Window:addTransition(effect, args)
|
||||||
if self.parent then
|
if self.parent then
|
||||||
args = args or { }
|
args = args or { }
|
||||||
@ -2058,6 +2072,88 @@ function UI.TitleBar:eventHandler(event)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[-- Button --]]--
|
||||||
|
UI.Button = class(UI.Window)
|
||||||
|
UI.Button.defaults = {
|
||||||
|
UIElement = 'Button',
|
||||||
|
text = 'button',
|
||||||
|
backgroundColor = colors.gray,
|
||||||
|
backgroundFocusColor = colors.lightGray,
|
||||||
|
textFocusColor = colors.white,
|
||||||
|
textInactiveColor = colors.gray,
|
||||||
|
textColor = colors.white,
|
||||||
|
centered = true,
|
||||||
|
height = 1,
|
||||||
|
focusIndicator = ' ',
|
||||||
|
event = 'button_press',
|
||||||
|
accelerators = {
|
||||||
|
space = 'button_activate',
|
||||||
|
enter = 'button_activate',
|
||||||
|
mouse_click = 'button_activate',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function UI.Button:init(args)
|
||||||
|
local defaults = UI:getDefaults(UI.Button, args)
|
||||||
|
UI.Window.init(self, defaults)
|
||||||
|
end
|
||||||
|
|
||||||
|
function UI.Button:setParent()
|
||||||
|
if not self.width and not self.ex then
|
||||||
|
self.width = #self.text + 2
|
||||||
|
end
|
||||||
|
UI.Window.setParent(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function UI.Button:draw()
|
||||||
|
local fg = self.textColor
|
||||||
|
local bg = self.backgroundColor
|
||||||
|
local ind = ' '
|
||||||
|
if self.focused then
|
||||||
|
bg = self.backgroundFocusColor
|
||||||
|
fg = self.textFocusColor
|
||||||
|
ind = self.focusIndicator
|
||||||
|
elseif self.inactive then
|
||||||
|
fg = self.textInactiveColor
|
||||||
|
end
|
||||||
|
local text = ind .. self.text .. ' '
|
||||||
|
if self.centered then
|
||||||
|
self:clear(bg)
|
||||||
|
self:centeredWrite(1 + math.floor(self.height / 2), text, bg, fg)
|
||||||
|
else
|
||||||
|
self:write(1, 1, Util.widthify(text, self.width), bg, fg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function UI.Button:focus()
|
||||||
|
if self.focused then
|
||||||
|
self:scrollIntoView()
|
||||||
|
end
|
||||||
|
self:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
function UI.Button:eventHandler(event)
|
||||||
|
if event.type == 'button_activate' then
|
||||||
|
self:emit({ type = self.event, button = self })
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- MenuItem --]]--
|
||||||
|
UI.MenuItem = class(UI.Button)
|
||||||
|
UI.MenuItem.defaults = {
|
||||||
|
UIElement = 'MenuItem',
|
||||||
|
textColor = colors.black,
|
||||||
|
backgroundColor = colors.lightGray,
|
||||||
|
textFocusColor = colors.white,
|
||||||
|
backgroundFocusColor = colors.lightGray,
|
||||||
|
}
|
||||||
|
|
||||||
|
function UI.MenuItem:init(args)
|
||||||
|
local defaults = UI:getDefaults(UI.MenuItem, args)
|
||||||
|
UI.Button.init(self, defaults)
|
||||||
|
end
|
||||||
|
|
||||||
--[[-- MenuBar --]]--
|
--[[-- MenuBar --]]--
|
||||||
UI.MenuBar = class(UI.Window)
|
UI.MenuBar = class(UI.Window)
|
||||||
UI.MenuBar.defaults = {
|
UI.MenuBar.defaults = {
|
||||||
@ -2068,7 +2164,9 @@ UI.MenuBar.defaults = {
|
|||||||
textColor = colors.black,
|
textColor = colors.black,
|
||||||
spacing = 2,
|
spacing = 2,
|
||||||
showBackButton = false,
|
showBackButton = false,
|
||||||
|
buttonClass = 'MenuItem',
|
||||||
}
|
}
|
||||||
|
UI.MenuBar.spacer = { spacer = true, text = 'spacer', inactive = true }
|
||||||
|
|
||||||
function UI.MenuBar:init(args)
|
function UI.MenuBar:init(args)
|
||||||
local defaults = UI:getDefaults(UI.MenuBar, args)
|
local defaults = UI:getDefaults(UI.MenuBar, args)
|
||||||
@ -2090,10 +2188,17 @@ function UI.MenuBar:init(args)
|
|||||||
}
|
}
|
||||||
x = x + buttonProperties.width
|
x = x + buttonProperties.width
|
||||||
UI:setProperties(buttonProperties, button)
|
UI:setProperties(buttonProperties, button)
|
||||||
|
|
||||||
|
local parent = self
|
||||||
|
if button.dropdown then
|
||||||
|
buttonProperties.dropmenu = UI.DropMenu { buttons = button.dropdown }
|
||||||
|
table.insert(self.children, buttonProperties.dropmenu)
|
||||||
|
end
|
||||||
|
|
||||||
if button.name then
|
if button.name then
|
||||||
self[button.name] = UI.MenuItem(buttonProperties)
|
self[button.name] = UI[self.buttonClass](buttonProperties)
|
||||||
else
|
else
|
||||||
table.insert(self.children, UI.MenuItem(buttonProperties))
|
table.insert(self.children, UI[self.buttonClass](buttonProperties))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -2110,30 +2215,53 @@ function UI.MenuBar:init(args)
|
|||||||
UI.Window.init(self, defaults)
|
UI.Window.init(self, defaults)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function UI.MenuBar:getActive(menuItem)
|
||||||
|
return not menuItem.inactive
|
||||||
|
end
|
||||||
|
|
||||||
function UI.MenuBar:eventHandler(event)
|
function UI.MenuBar:eventHandler(event)
|
||||||
if event.type == 'dropdown' then
|
if event.type == 'button_press' and event.button.dropmenu then
|
||||||
-- better, but still a bad implementation
|
if event.button.dropmenu.enabled then
|
||||||
-- this at least will allow overrides
|
event.button.dropmenu:hide()
|
||||||
-- on the button and menubar
|
return true
|
||||||
if event.button and event.button.dropdown then
|
else
|
||||||
local dropdown = self.parent[event.button.dropdown]
|
local x, y = getPosition(event.button)
|
||||||
if dropdown then
|
if x + event.button.dropmenu.width > self.width then
|
||||||
if dropdown.enabled then
|
x = self.width - event.button.dropmenu.width + 1
|
||||||
dropdown:hide(event.button)
|
|
||||||
else
|
|
||||||
dropdown:show(event.button)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
|
for _,c in pairs(event.button.dropmenu.children) do
|
||||||
|
if not c.spacer then
|
||||||
|
c.inactive = not self:getActive(c)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
event.button.dropmenu:show(x, y + 1)
|
||||||
end
|
end
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[-- DropMenuItem --]]--
|
||||||
|
UI.DropMenuItem = class(UI.Button)
|
||||||
|
UI.DropMenuItem.defaults = {
|
||||||
|
UIElement = 'DropMenuItem',
|
||||||
|
textColor = colors.black,
|
||||||
|
backgroundColor = colors.white,
|
||||||
|
textFocusColor = colors.black,
|
||||||
|
textInactiveColor = colors.lightGray,
|
||||||
|
backgroundFocusColor = colors.lightGray,
|
||||||
|
}
|
||||||
|
|
||||||
|
function UI.DropMenuItem:init(args)
|
||||||
|
local defaults = UI:getDefaults(UI.DropMenuItem, args)
|
||||||
|
UI.Button.init(self, defaults)
|
||||||
|
end
|
||||||
|
|
||||||
--[[-- DropMenu --]]--
|
--[[-- DropMenu --]]--
|
||||||
UI.DropMenu = class(UI.MenuBar)
|
UI.DropMenu = class(UI.MenuBar)
|
||||||
UI.DropMenu.defaults = {
|
UI.DropMenu.defaults = {
|
||||||
UIElement = 'DropMenu',
|
UIElement = 'DropMenu',
|
||||||
backgroundColor = colors.lightGray,
|
backgroundColor = colors.white,
|
||||||
|
buttonClass = 'DropMenuItem',
|
||||||
}
|
}
|
||||||
function UI.DropMenu:init(args)
|
function UI.DropMenu:init(args)
|
||||||
local defaults = UI:getDefaults(UI.DropMenu, args)
|
local defaults = UI:getDefaults(UI.DropMenu, args)
|
||||||
@ -2153,36 +2281,41 @@ function UI.DropMenu:setParent()
|
|||||||
end
|
end
|
||||||
for _,child in ipairs(self.children) do
|
for _,child in ipairs(self.children) do
|
||||||
child.width = maxWidth + 2
|
child.width = maxWidth + 2
|
||||||
|
if child.spacer then
|
||||||
|
child.text = string.rep('-', child.width - 2)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.height = #self.children
|
self.height = #self.children + 1
|
||||||
self.width = maxWidth + 2
|
self.width = maxWidth + 2
|
||||||
self.ow = self.width
|
self.ow = self.width
|
||||||
|
|
||||||
|
self.canvas = self:addLayer()
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.DropMenu:enable()
|
function UI.DropMenu:enable()
|
||||||
self.enabled = false
|
self.enabled = false
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.DropMenu:show(button) -- the x, y should be passed instead of button
|
function UI.DropMenu:show(x, y)
|
||||||
self.button = button
|
|
||||||
self.x, self.y = getPosition(button)
|
self.x, self.y = x, y
|
||||||
self.y = self.y + 1
|
self.canvas:move(x, y)
|
||||||
if self.x + self.width > self.parent.width then
|
self.canvas:setVisible(true)
|
||||||
self.x = self.parent.width - self.width + 1
|
|
||||||
end
|
|
||||||
self.enabled = true
|
self.enabled = true
|
||||||
for _,child in ipairs(self.children) do
|
for _,child in ipairs(self.children) do
|
||||||
child:enable()
|
child:enable()
|
||||||
end
|
end
|
||||||
self:setFocus(self.children[1])
|
|
||||||
|
self:focusFirst()
|
||||||
self:draw()
|
self:draw()
|
||||||
UI:capture(self)
|
UI:capture(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.DropMenu:hide()
|
function UI.DropMenu:hide()
|
||||||
self:disable()
|
self:disable()
|
||||||
self.parent:draw()
|
self.canvas:setVisible(false)
|
||||||
UI:release(self)
|
UI:release(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -2641,85 +2774,6 @@ function UI.VerticalMeter:draw()
|
|||||||
self:clearArea(1, height + 1, self.width, self.height, self.meterColor)
|
self:clearArea(1, height + 1, self.width, self.height, self.meterColor)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[-- Button --]]--
|
|
||||||
UI.Button = class(UI.Window)
|
|
||||||
UI.Button.defaults = {
|
|
||||||
UIElement = 'Button',
|
|
||||||
text = 'button',
|
|
||||||
backgroundColor = colors.gray,
|
|
||||||
backgroundFocusColor = colors.lightGray,
|
|
||||||
textFocusColor = colors.white,
|
|
||||||
textColor = colors.white,
|
|
||||||
centered = true,
|
|
||||||
height = 1,
|
|
||||||
focusIndicator = ' ',
|
|
||||||
event = 'button_press',
|
|
||||||
accelerators = {
|
|
||||||
space = 'button_activate',
|
|
||||||
enter = 'button_activate',
|
|
||||||
mouse_click = 'button_activate',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function UI.Button:init(args)
|
|
||||||
local defaults = UI:getDefaults(UI.Button, args)
|
|
||||||
UI.Window.init(self, defaults)
|
|
||||||
end
|
|
||||||
|
|
||||||
function UI.Button:setParent()
|
|
||||||
if not self.width and not self.ex then
|
|
||||||
self.width = #self.text + 2
|
|
||||||
end
|
|
||||||
UI.Window.setParent(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function UI.Button:draw()
|
|
||||||
local fg = self.textColor
|
|
||||||
local bg = self.backgroundColor
|
|
||||||
local ind = ' '
|
|
||||||
if self.focused then
|
|
||||||
bg = self.backgroundFocusColor
|
|
||||||
fg = self.textFocusColor
|
|
||||||
ind = self.focusIndicator
|
|
||||||
end
|
|
||||||
local text = ind .. self.text .. ' '
|
|
||||||
if self.centered then
|
|
||||||
self:clear(bg)
|
|
||||||
self:centeredWrite(1 + math.floor(self.height / 2), text, bg, fg)
|
|
||||||
else
|
|
||||||
self:write(1, 1, Util.widthify(text, self.width), bg, fg)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function UI.Button:focus()
|
|
||||||
if self.focused then
|
|
||||||
self:scrollIntoView()
|
|
||||||
end
|
|
||||||
self:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
function UI.Button:eventHandler(event)
|
|
||||||
if event.type == 'button_activate' then
|
|
||||||
self:emit({ type = self.event, button = self })
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- MenuItem --]]--
|
|
||||||
UI.MenuItem = class(UI.Button)
|
|
||||||
UI.MenuItem.defaults = {
|
|
||||||
UIElement = 'MenuItem',
|
|
||||||
textColor = colors.black,
|
|
||||||
backgroundColor = colors.lightGray,
|
|
||||||
textFocusColor = colors.white,
|
|
||||||
backgroundFocusColor = colors.lightGray,
|
|
||||||
}
|
|
||||||
|
|
||||||
function UI.MenuItem:init(args)
|
|
||||||
local defaults = UI:getDefaults(UI.MenuItem, args)
|
|
||||||
UI.Button.init(self, defaults)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- TextEntry --]]--
|
--[[-- TextEntry --]]--
|
||||||
UI.TextEntry = class(UI.Window)
|
UI.TextEntry = class(UI.Window)
|
||||||
UI.TextEntry.defaults = {
|
UI.TextEntry.defaults = {
|
||||||
|
@ -44,6 +44,12 @@ function Canvas:init(args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Canvas:move(x, y)
|
||||||
|
self.x, self.y = x, y
|
||||||
|
self.ex = self.x + self.width - 1
|
||||||
|
self.ey = self.y + self.height - 1
|
||||||
|
end
|
||||||
|
|
||||||
function Canvas:resize(w, h)
|
function Canvas:resize(w, h)
|
||||||
for i = self.height, h do
|
for i = self.height, h do
|
||||||
self.lines[i] = { }
|
self.lines[i] = { }
|
||||||
|
@ -32,43 +32,31 @@ end
|
|||||||
local Browser = UI.Page {
|
local Browser = UI.Page {
|
||||||
menuBar = UI.MenuBar {
|
menuBar = UI.MenuBar {
|
||||||
buttons = {
|
buttons = {
|
||||||
{ text = '^-', event = 'updir' },
|
{ text = '^-', event = 'updir' },
|
||||||
{ text = 'File', event = 'dropdown', dropdown = 'fileMenu' },
|
{ text = 'File', dropdown = {
|
||||||
{ text = 'Edit', event = 'dropdown', dropdown = 'editMenu' },
|
{ text = 'Run', event = 'run' },
|
||||||
{ text = 'View', event = 'dropdown', dropdown = 'viewMenu' },
|
{ text = 'Edit e', event = 'edit' },
|
||||||
|
{ text = 'Shell s', event = 'shell' },
|
||||||
|
UI.MenuBar.spacer,
|
||||||
|
{ text = 'Quit q', event = 'quit' },
|
||||||
|
} },
|
||||||
|
{ text = 'Edit', dropdown = {
|
||||||
|
{ text = 'Cut ^x', event = 'cut' },
|
||||||
|
{ text = 'Copy ^c', event = 'copy' },
|
||||||
|
{ text = 'Paste ^v', event = 'paste' },
|
||||||
|
UI.MenuBar.spacer,
|
||||||
|
{ text = 'Mark m', event = 'mark' },
|
||||||
|
{ text = 'Unmark all u', event = 'unmark' },
|
||||||
|
UI.MenuBar.spacer,
|
||||||
|
{ text = 'Delete del', event = 'delete' },
|
||||||
|
} },
|
||||||
|
{ text = 'View', dropdown = {
|
||||||
|
{ text = 'Refresh r', event = 'refresh' },
|
||||||
|
{ text = 'Hidden ^h', event = 'toggle_hidden' },
|
||||||
|
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
|
||||||
|
} },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fileMenu = UI.DropMenu {
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Run', event = 'run' },
|
|
||||||
{ text = 'Edit e', event = 'edit' },
|
|
||||||
{ text = 'Shell s', event = 'shell' },
|
|
||||||
UI.Text { value = ' ------------ ' },
|
|
||||||
{ text = 'Quit q', event = 'quit' },
|
|
||||||
UI.Text { },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
editMenu = UI.DropMenu {
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Cut ^x', event = 'cut' },
|
|
||||||
{ text = 'Copy ^c', event = 'copy' },
|
|
||||||
{ text = 'Paste ^v', event = 'paste' },
|
|
||||||
UI.Text { value = ' --------------- ' },
|
|
||||||
{ text = 'Mark m', event = 'mark' },
|
|
||||||
{ text = 'Unmark all u', event = 'unmark' },
|
|
||||||
UI.Text { value = ' --------------- ' },
|
|
||||||
{ text = 'Delete del', event = 'delete' },
|
|
||||||
UI.Text { },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
viewMenu = UI.DropMenu {
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Refresh r', event = 'refresh' },
|
|
||||||
{ text = 'Hidden ^h', event = 'toggle_hidden' },
|
|
||||||
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
|
|
||||||
UI.Text { },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Name', key = 'name' },
|
{ heading = 'Name', key = 'name' },
|
||||||
@ -107,6 +95,16 @@ function Browser:enable()
|
|||||||
self:setFocus(self.grid)
|
self:setFocus(self.grid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Browser.menuBar:getActive(menuItem)
|
||||||
|
local file = Browser.grid:getSelected()
|
||||||
|
if file then
|
||||||
|
if menuItem.event == 'edit' or menuItem.event == 'run' then
|
||||||
|
return not file.isDir
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
function Browser.grid:sortCompare(a, b)
|
function Browser.grid:sortCompare(a, b)
|
||||||
if self.sortColumn == 'fsize' then
|
if self.sortColumn == 'fsize' then
|
||||||
return a.size < b.size
|
return a.size < b.size
|
||||||
|
Loading…
Reference in New Issue
Block a user