Drawing optimization
This commit is contained in:
parent
ab6052705a
commit
f7710ca912
@ -276,7 +276,12 @@ mod tests {
|
|||||||
let line_height = 30.0;
|
let line_height = 30.0;
|
||||||
let line_y = 0.0;
|
let line_y = 0.0;
|
||||||
|
|
||||||
let (y, width, height) = cursor_rect(&mode, char_width, line_height, line_y, false);
|
let (y, width, height) = cursor_rect(
|
||||||
|
&mode,
|
||||||
|
&CellMetrics::new_hw(line_height, char_width),
|
||||||
|
line_y,
|
||||||
|
false,
|
||||||
|
);
|
||||||
assert_eq!(line_y + line_height - line_height / 4.0, y);
|
assert_eq!(line_y + line_height - line_height / 4.0, y);
|
||||||
assert_eq!(char_width, width);
|
assert_eq!(char_width, width);
|
||||||
assert_eq!(line_height / 4.0, height);
|
assert_eq!(line_height / 4.0, height);
|
||||||
@ -295,7 +300,12 @@ mod tests {
|
|||||||
let line_height = 30.0;
|
let line_height = 30.0;
|
||||||
let line_y = 0.0;
|
let line_y = 0.0;
|
||||||
|
|
||||||
let (y, width, height) = cursor_rect(&mode, char_width, line_height, line_y, true);
|
let (y, width, height) = cursor_rect(
|
||||||
|
&mode,
|
||||||
|
&CellMetrics::new_hw(line_height, char_width),
|
||||||
|
line_y,
|
||||||
|
true,
|
||||||
|
);
|
||||||
assert_eq!(line_y + line_height - line_height / 4.0, y);
|
assert_eq!(line_y + line_height - line_height / 4.0, y);
|
||||||
assert_eq!(char_width * 2.0, width);
|
assert_eq!(char_width * 2.0, width);
|
||||||
assert_eq!(line_height / 4.0, height);
|
assert_eq!(line_height / 4.0, height);
|
||||||
@ -314,7 +324,12 @@ mod tests {
|
|||||||
let line_height = 30.0;
|
let line_height = 30.0;
|
||||||
let line_y = 0.0;
|
let line_y = 0.0;
|
||||||
|
|
||||||
let (y, width, height) = cursor_rect(&mode, char_width, line_height, line_y, false);
|
let (y, width, height) = cursor_rect(
|
||||||
|
&mode,
|
||||||
|
&CellMetrics::new_hw(line_height, char_width),
|
||||||
|
line_y,
|
||||||
|
false,
|
||||||
|
);
|
||||||
assert_eq!(line_y, y);
|
assert_eq!(line_y, y);
|
||||||
assert_eq!(char_width / 4.0, width);
|
assert_eq!(char_width / 4.0, width);
|
||||||
assert_eq!(line_height, height);
|
assert_eq!(line_height, height);
|
||||||
|
@ -3,8 +3,10 @@ use pango::prelude::*;
|
|||||||
use pango;
|
use pango;
|
||||||
|
|
||||||
use sys::pango as sys_pango;
|
use sys::pango as sys_pango;
|
||||||
|
use sys::pango::AttrIteratorFactory;
|
||||||
|
|
||||||
use ui_model::StyledLine;
|
use ui_model::StyledLine;
|
||||||
|
use super::itemize::ItemizeIterator;
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
state: ContextState,
|
state: ContextState,
|
||||||
@ -20,11 +22,20 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn itemize(&self, line: &StyledLine) -> Vec<sys_pango::Item> {
|
pub fn itemize(&self, line: &StyledLine) -> Vec<sys_pango::Item> {
|
||||||
sys_pango::pango_itemize(
|
let mut attr_iter = line.attr_list.get_iterator();
|
||||||
&self.state.pango_context,
|
|
||||||
line.line_str.trim_right(),
|
ItemizeIterator::new(&line.line_str)
|
||||||
&line.attr_list,
|
.flat_map(|(offset, len)| {
|
||||||
)
|
sys_pango::pango_itemize(
|
||||||
|
&self.state.pango_context,
|
||||||
|
&line.line_str,
|
||||||
|
offset,
|
||||||
|
len,
|
||||||
|
&line.attr_list,
|
||||||
|
Some(&mut attr_iter),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -94,4 +105,18 @@ impl CellMetrics {
|
|||||||
pango::SCALE as f64,
|
pango::SCALE as f64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn new_hw(line_height: f64, char_width: f64) -> Self {
|
||||||
|
CellMetrics {
|
||||||
|
pango_ascent: 0,
|
||||||
|
pango_descent: 0,
|
||||||
|
pango_char_width: 0,
|
||||||
|
ascent: 0.0,
|
||||||
|
line_height,
|
||||||
|
char_width,
|
||||||
|
underline_position: 0.0,
|
||||||
|
underline_thickness: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
58
src/render/itemize.rs
Normal file
58
src/render/itemize.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use std::str::CharIndices;
|
||||||
|
|
||||||
|
pub struct ItemizeIterator <'a> {
|
||||||
|
char_iter: CharIndices<'a>,
|
||||||
|
line: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <'a> ItemizeIterator <'a> {
|
||||||
|
pub fn new(line: &'a str) -> Self {
|
||||||
|
ItemizeIterator {
|
||||||
|
char_iter: line.char_indices(),
|
||||||
|
line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <'a> Iterator for ItemizeIterator<'a> {
|
||||||
|
type Item = (usize, usize);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let mut start_index = None;
|
||||||
|
|
||||||
|
let end_index = loop {
|
||||||
|
if let Some((index, ch)) = self.char_iter.next() {
|
||||||
|
let is_whitespace = ch.is_whitespace();
|
||||||
|
|
||||||
|
if start_index.is_none() && !is_whitespace {
|
||||||
|
start_index = Some(index);
|
||||||
|
}
|
||||||
|
if start_index.is_some() && is_whitespace {
|
||||||
|
break index;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break self.line.len();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(start_index) = start_index {
|
||||||
|
Some((start_index, end_index - start_index))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iterator() {
|
||||||
|
let mut iter = ItemizeIterator::new("Test line ");
|
||||||
|
|
||||||
|
assert_eq!(Some((0, 4)), iter.next());
|
||||||
|
assert_eq!(Some((6, 4)), iter.next());
|
||||||
|
assert_eq!(None, iter.next());
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
mod context;
|
mod context;
|
||||||
|
mod itemize;
|
||||||
|
|
||||||
pub use self::context::Context;
|
pub use self::context::Context;
|
||||||
pub use self::context::CellMetrics;
|
pub use self::context::CellMetrics;
|
||||||
@ -77,6 +78,7 @@ pub fn render(
|
|||||||
|
|
||||||
if cell.attrs.underline || cell.attrs.undercurl {
|
if cell.attrs.underline || cell.attrs.undercurl {
|
||||||
if cell.attrs.undercurl {
|
if cell.attrs.undercurl {
|
||||||
|
// TODO: properly draw undercurl
|
||||||
let sp = color_model.actual_cell_sp(cell);
|
let sp = color_model.actual_cell_sp(cell);
|
||||||
ctx.set_source_rgba(sp.0, sp.1, sp.2, 0.7);
|
ctx.set_source_rgba(sp.0, sp.1, sp.2, 0.7);
|
||||||
ctx.set_dash(&[4.0, 2.0], 0.0);
|
ctx.set_dash(&[4.0, 2.0], 0.0);
|
||||||
|
190
src/shell.rs
190
src/shell.rs
@ -679,60 +679,6 @@ fn init_nvim(state_arc: &Arc<UiMutex<State>>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//#[inline]
|
|
||||||
//fn get_model_clip(
|
|
||||||
// state: &State,
|
|
||||||
// line_height: f64,
|
|
||||||
// char_width: f64,
|
|
||||||
// clip: (f64, f64, f64, f64),
|
|
||||||
//) -> ModelRect {
|
|
||||||
// let mut model_clip =
|
|
||||||
// ModelRect::from_area(line_height, char_width, clip.0, clip.1, clip.2, clip.3);
|
|
||||||
// // in some cases symbols from previous row affect next row
|
|
||||||
// // for example underscore symbol or 'g'
|
|
||||||
// // also for italic text it is possible that symbol can affect next one
|
|
||||||
// // see deference between logical rect and ink rect
|
|
||||||
// model_clip.extend(1, 0, 1, 0);
|
|
||||||
// state.model.limit_to_model(&mut model_clip);
|
|
||||||
//
|
|
||||||
// model_clip
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//#[inline]
|
|
||||||
//fn draw_backgound(
|
|
||||||
// state: &State,
|
|
||||||
// draw_bitmap: &ModelBitamp,
|
|
||||||
// ctx: &cairo::Context,
|
|
||||||
// line_height: f64,
|
|
||||||
// char_width: f64,
|
|
||||||
// model_clip: &ModelRect,
|
|
||||||
//) {
|
|
||||||
// let line_x = model_clip.left as f64 * char_width;
|
|
||||||
// let mut line_y: f64 = model_clip.top as f64 * line_height;
|
|
||||||
//
|
|
||||||
// for (line_idx, line) in state.model.clip_model(model_clip) {
|
|
||||||
// ctx.move_to(line_x, line_y);
|
|
||||||
//
|
|
||||||
// for (col_idx, cell) in line.iter() {
|
|
||||||
// let current_point = ctx.get_current_point();
|
|
||||||
//
|
|
||||||
// if !draw_bitmap.get(col_idx, line_idx) {
|
|
||||||
// let (bg, _) = state.colors(cell);
|
|
||||||
//
|
|
||||||
// if &state.bg_color != bg {
|
|
||||||
// ctx.set_source_rgb(bg.0, bg.1, bg.2);
|
|
||||||
// ctx.rectangle(current_point.0, current_point.1, char_width, line_height);
|
|
||||||
// ctx.fill();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ctx.move_to(current_point.0 + char_width, current_point.1);
|
|
||||||
// }
|
|
||||||
// line_y += line_height;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
fn draw_initializing(state: &State, ctx: &cairo::Context) {
|
fn draw_initializing(state: &State, ctx: &cairo::Context) {
|
||||||
let layout = ctx.create_pango_layout();
|
let layout = ctx.create_pango_layout();
|
||||||
let desc = state.get_font_desc();
|
let desc = state.get_font_desc();
|
||||||
@ -773,142 +719,6 @@ fn draw_initializing(state: &State, ctx: &cairo::Context) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//fn draw(state: &State, ctx: &cairo::Context) {
|
|
||||||
// let layout = ctx.create_pango_layout();
|
|
||||||
// let mut desc = state.create_pango_font();
|
|
||||||
// let mut buf = String::with_capacity(4);
|
|
||||||
//
|
|
||||||
// let (row, col) = state.model.get_cursor();
|
|
||||||
//
|
|
||||||
// let line_height = state.line_height.unwrap();
|
|
||||||
// let char_width = state.char_width.unwrap();
|
|
||||||
// let mut draw_bitmap = ModelBitamp::new(state.model.columns, state.model.rows);
|
|
||||||
//
|
|
||||||
// ctx.set_source_rgb(state.bg_color.0, state.bg_color.1, state.bg_color.2);
|
|
||||||
// ctx.paint();
|
|
||||||
//
|
|
||||||
// let clip_rects = &ctx.copy_clip_rectangle_list().rectangles;
|
|
||||||
// for clip_idx in 0..clip_rects.len() {
|
|
||||||
// let clip = clip_rects.get(clip_idx).unwrap();
|
|
||||||
//
|
|
||||||
// let model_clip = get_model_clip(state, line_height, char_width, (
|
|
||||||
// clip.x,
|
|
||||||
// clip.y,
|
|
||||||
// clip.x + clip.width,
|
|
||||||
// clip.y + clip.height,
|
|
||||||
// ));
|
|
||||||
//
|
|
||||||
// let line_x = model_clip.left as f64 * char_width;
|
|
||||||
// let mut line_y: f64 = model_clip.top as f64 * line_height;
|
|
||||||
//
|
|
||||||
// draw_backgound(
|
|
||||||
// state,
|
|
||||||
// &draw_bitmap,
|
|
||||||
// ctx,
|
|
||||||
// line_height,
|
|
||||||
// char_width,
|
|
||||||
// &model_clip,
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// for (line_idx, line) in state.model.clip_model(&model_clip) {
|
|
||||||
//
|
|
||||||
// ctx.move_to(line_x, line_y);
|
|
||||||
//
|
|
||||||
// for (col_idx, cell) in line.iter() {
|
|
||||||
// let current_point = ctx.get_current_point();
|
|
||||||
//
|
|
||||||
// if !draw_bitmap.get(col_idx, line_idx) {
|
|
||||||
// let double_width = line.is_double_width(col_idx);
|
|
||||||
//
|
|
||||||
// let (bg, fg) = state.colors(cell);
|
|
||||||
//
|
|
||||||
// if row == line_idx && col == col_idx {
|
|
||||||
// state.cursor.as_ref().unwrap().draw(
|
|
||||||
// ctx,
|
|
||||||
// state,
|
|
||||||
// char_width,
|
|
||||||
// line_height,
|
|
||||||
// line_y,
|
|
||||||
// double_width,
|
|
||||||
// bg,
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// ctx.move_to(current_point.0, current_point.1);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// if !cell.ch.is_whitespace() {
|
|
||||||
// update_font_description(&mut desc, &cell.attrs);
|
|
||||||
//
|
|
||||||
// layout.set_font_description(&desc);
|
|
||||||
// buf.clear();
|
|
||||||
// buf.push(cell.ch);
|
|
||||||
// layout.set_text(&buf);
|
|
||||||
//
|
|
||||||
// // correct layout for double_width chars
|
|
||||||
// if double_width {
|
|
||||||
// let (dw_width, dw_height) = layout.get_pixel_size();
|
|
||||||
// let x_offset = (char_width * 2.0 - dw_width as f64) / 2.0;
|
|
||||||
// let y_offset = (line_height - dw_height as f64) / 2.0;
|
|
||||||
// ctx.rel_move_to(x_offset, y_offset);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ctx.set_source_rgb(fg.0, fg.1, fg.2);
|
|
||||||
// ctx.update_pango_layout(&layout);
|
|
||||||
// ctx.show_pango_layout(&layout);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if cell.attrs.underline || cell.attrs.undercurl {
|
|
||||||
// // [TODO]: Current gtk-rs bindings does not provide fontmetrics access
|
|
||||||
// // so it is not possible to find right position for underline or undercurl position
|
|
||||||
// // > update_font_description(&mut desc, &cell.attrs);
|
|
||||||
// // > layout.get_context().unwrap().get_metrics();
|
|
||||||
// let top_offset = line_height * 0.9;
|
|
||||||
//
|
|
||||||
// if cell.attrs.undercurl {
|
|
||||||
// let sp = if let Some(ref sp) = cell.attrs.special {
|
|
||||||
// sp
|
|
||||||
// } else {
|
|
||||||
// &state.sp_color
|
|
||||||
// };
|
|
||||||
// ctx.set_source_rgba(sp.0, sp.1, sp.2, 0.7);
|
|
||||||
// ctx.set_dash(&[4.0, 2.0], 0.0);
|
|
||||||
// ctx.set_line_width(2.0);
|
|
||||||
// ctx.move_to(current_point.0, line_y + top_offset);
|
|
||||||
// ctx.line_to(current_point.0 + char_width, line_y + top_offset);
|
|
||||||
// ctx.stroke();
|
|
||||||
// ctx.set_dash(&[], 0.0);
|
|
||||||
// } else if cell.attrs.underline {
|
|
||||||
// ctx.set_source_rgb(fg.0, fg.1, fg.2);
|
|
||||||
// ctx.set_line_width(1.0);
|
|
||||||
// ctx.move_to(current_point.0, line_y + top_offset);
|
|
||||||
// ctx.line_to(current_point.0 + char_width, line_y + top_offset);
|
|
||||||
// ctx.stroke();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ctx.move_to(current_point.0 + char_width, current_point.1);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// line_y += line_height;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// draw_bitmap.fill_from_model(&model_clip);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//#[inline]
|
|
||||||
//fn update_font_description(desc: &mut FontDescription, attrs: &Attrs) {
|
|
||||||
// desc.unset_fields(pango::FONT_MASK_STYLE | pango::FONT_MASK_WEIGHT);
|
|
||||||
// if attrs.italic {
|
|
||||||
// desc.set_style(pango::Style::Italic);
|
|
||||||
// }
|
|
||||||
// if attrs.bold {
|
|
||||||
// desc.set_weight(pango::Weight::Bold);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
fn request_window_resize(state: &mut State) {
|
fn request_window_resize(state: &mut State) {
|
||||||
if !state.request_resize {
|
if !state.request_resize {
|
||||||
return;
|
return;
|
||||||
|
31
src/sys/pango/attr_iterator.rs
Normal file
31
src/sys/pango/attr_iterator.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use std::ptr;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use pango_sys;
|
||||||
|
use pango;
|
||||||
|
|
||||||
|
use glib_ffi;
|
||||||
|
use glib::translate::*;
|
||||||
|
|
||||||
|
glib_wrapper! {
|
||||||
|
pub struct AttrIterator(Boxed<pango_sys::PangoAttrIterator>);
|
||||||
|
|
||||||
|
match fn {
|
||||||
|
copy => |ptr| pango_sys::pango_attr_iterator_copy(ptr as *mut _),
|
||||||
|
free => |ptr| pango_sys::pango_attr_iterator_destroy(ptr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AttrIteratorFactory {
|
||||||
|
fn get_iterator(&self) -> AttrIterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttrIteratorFactory for pango::AttrList {
|
||||||
|
fn get_iterator(&self) -> AttrIterator {
|
||||||
|
unsafe {
|
||||||
|
from_glib_none(pango_sys::pango_attr_list_get_iterator(
|
||||||
|
self.to_glib_none().0,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
mod item;
|
mod item;
|
||||||
mod analysis;
|
mod analysis;
|
||||||
|
mod attr_iterator;
|
||||||
|
|
||||||
pub use self::item::Item;
|
pub use self::item::Item;
|
||||||
pub use self::analysis::Analysis;
|
pub use self::analysis::Analysis;
|
||||||
|
pub use self::attr_iterator::{AttrIterator, AttrIteratorFactory};
|
||||||
|
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
@ -14,16 +16,19 @@ use glib::translate::*;
|
|||||||
pub fn pango_itemize(
|
pub fn pango_itemize(
|
||||||
context: &pango::Context,
|
context: &pango::Context,
|
||||||
text: &str,
|
text: &str,
|
||||||
attrs: &pango::AttrList
|
start_index: usize,
|
||||||
|
length: usize,
|
||||||
|
attrs: &pango::AttrList,
|
||||||
|
cached_iter: Option<&mut AttrIterator>,
|
||||||
) -> Vec<Item> {
|
) -> Vec<Item> {
|
||||||
unsafe {
|
unsafe {
|
||||||
FromGlibPtrContainer::from_glib_container(pango_sys::pango_itemize(
|
FromGlibPtrContainer::from_glib_container(pango_sys::pango_itemize(
|
||||||
context.to_glib_none().0,
|
context.to_glib_none().0,
|
||||||
text.as_ptr() as *const i8,
|
text.as_ptr() as *const i8,
|
||||||
0,
|
start_index as i32,
|
||||||
text.len() as i32,
|
length as i32,
|
||||||
attrs.to_glib_none().0,
|
attrs.to_glib_none().0,
|
||||||
ptr::null_mut(),
|
cached_iter.map(|iter| iter.to_glib_none_mut().0).unwrap_or(ptr::null_mut()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,17 +292,6 @@ mod tests {
|
|||||||
assert_eq!(19, rect.right);
|
assert_eq!(19, rect.right);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_repaint_rect() {
|
|
||||||
let rect = ModelRect::point(1, 1);
|
|
||||||
let (x, y, width, height) = rect.to_area(10.0, 5.0);
|
|
||||||
|
|
||||||
assert_eq!(5, x);
|
|
||||||
assert_eq!(10, y);
|
|
||||||
assert_eq!(5, width);
|
|
||||||
assert_eq!(10, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_put_area() {
|
fn test_put_area() {
|
||||||
let mut model = UiModel::new(10, 20);
|
let mut model = UiModel::new(10, 20);
|
||||||
|
@ -263,3 +263,19 @@ impl AsRef<ModelRect> for ModelRect {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_repaint_rect() {
|
||||||
|
let rect = ModelRect::point(1, 1);
|
||||||
|
let (x, y, width, height) = rect.to_area(&CellMetrics::new_hw(10.0, 5.0));
|
||||||
|
|
||||||
|
assert_eq!(5, x);
|
||||||
|
assert_eq!(10, y);
|
||||||
|
assert_eq!(5, width);
|
||||||
|
assert_eq!(10, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user