use crate::*;
use crate::Debugger;
#[derive(Clone, Default)]
pub struct View {
pub view_id: Option<usize>,
pub(crate) redraw_id: u64,
pub(crate) is_overlay: bool,
debugger: Debugger,
}
impl View {
#[must_use]
pub fn with_is_overlay(self, is_overlay: bool) -> Self {
Self { is_overlay, ..self }
}
pub fn begin_view(&mut self, cx: &mut Cx, layout_size: LayoutSize) {
self.begin_view_with_layout(cx, Layout { direction: Direction::Down, layout_size, ..Layout::default() });
}
fn begin_view_with_layout(&mut self, cx: &mut Cx, layout: Layout) {
if !cx.in_redraw_cycle {
panic!("calling begin_view outside of redraw cycle is not possible!");
}
assert!(cx.shader_group_instance_offsets.is_empty(), "Can't use begin_view inside a shader group");
let pass_id = *cx.pass_stack.last().expect("No pass found when begin_view");
if self.view_id.is_none() {
self.view_id = Some(cx.views.len());
cx.views.push(CxView::default());
let cxview = &mut cx.views[self.view_id.unwrap()];
cxview.redraw_id = cx.redraw_id;
cxview.pass_id = pass_id;
}
let view_id = self.view_id.unwrap();
let (override_layout, is_root_for_pass) = if cx.passes[pass_id].main_view_id.is_none() {
let cxpass = &mut cx.passes[pass_id];
cxpass.main_view_id = Some(view_id);
(Layout { absolute: true, abs_size: Some(cxpass.pass_size), ..layout }, true)
} else {
(layout, false)
};
let cxpass = &mut cx.passes[pass_id];
let parent_view_id = if self.is_overlay {
if cxpass.main_view_id.is_none() {
panic!("Cannot make overlay inside window without root view")
};
cxpass.main_view_id.unwrap()
} else if is_root_for_pass {
view_id
} else if let Some(last_view_id) = cx.view_stack.last() {
*last_view_id
} else {
view_id
};
if view_id != parent_view_id {
let parent_cxview = &mut cx.views[parent_view_id];
let id = parent_cxview.draw_calls_len;
parent_cxview.draw_calls_len += 1;
if parent_cxview.draw_calls_len > parent_cxview.draw_calls.len() {
parent_cxview.draw_calls.push({
DrawCall {
view_id: parent_view_id,
draw_call_id: parent_cxview.draw_calls.len(),
redraw_id: cx.redraw_id,
sub_view_id: view_id,
..Default::default()
}
})
} else {
let draw = &mut parent_cxview.draw_calls[id];
draw.sub_view_id = view_id;
draw.redraw_id = cx.redraw_id;
}
}
cx.begin_typed_box(CxBoxType::View, override_layout);
let cxview = &mut cx.views[view_id];
assert_eq!(cxview.pass_id, pass_id);
self.redraw_id = cx.redraw_id;
cxview.redraw_id = cx.redraw_id;
cxview.draw_calls_len = 0;
cx.view_stack.push(view_id);
if is_root_for_pass {
cx.passes[pass_id].paint_dirty = true;
}
}
fn is_main_view(view_id: usize, cx: &mut Cx) -> bool {
if let Some(window_id) = cx.window_stack.last() {
if let Some(main_pass_id) = cx.windows[*window_id].main_pass_id {
let pass_id = cx.views[view_id].pass_id;
let cxpass = &cx.passes[pass_id];
if let Some(main_view_id) = cxpass.main_view_id {
if main_view_id == view_id && main_pass_id == pass_id {
return true;
}
}
}
}
false
}
pub fn end_view(&mut self, cx: &mut Cx) -> Area {
assert!(cx.shader_group_instance_offsets.is_empty(), "Can't use end_view inside a shader group");
let view_id = self.view_id.unwrap();
if cx.debug_flags.enable_layout_debugger && View::is_main_view(view_id, cx) {
self.debugger.draw(cx);
}
let view_area = Area::View(ViewArea { view_id, redraw_id: cx.redraw_id });
cx.layout_box_align_list.push(view_area);
let rect = cx.end_typed_box(CxBoxType::View);
cx.views[view_id].rect = rect;
cx.view_stack.pop();
view_area
}
pub fn get_rect(&self, cx: &Cx) -> Rect {
if let Some(view_id) = self.view_id {
let cxview = &cx.views[view_id];
return cxview.rect;
}
Rect::default()
}
pub fn area(&self) -> Area {
if let Some(view_id) = self.view_id {
Area::View(ViewArea { view_id, redraw_id: self.redraw_id })
} else {
Area::Empty
}
}
pub fn get_scroll_pos(&self, cx: &Cx) -> Vec2 {
if let Some(view_id) = self.view_id {
let cxview = &cx.views[view_id];
cxview.unsnapped_scroll
} else {
Vec2::default()
}
}
}
impl Cx {
fn create_draw_call(&mut self, shader_id: usize, props: DrawCallProps) -> &mut DrawCall {
assert!(self.in_redraw_cycle, "Must be in redraw cycle to append to draw calls");
let sh = &self.shaders[shader_id];
let current_view_id = *self.view_stack.last().unwrap();
let cxview = &mut self.views[current_view_id];
let draw_call_id = cxview.draw_calls_len;
if props.is_batchable() {
let shader_group_size = self.shader_group_instance_offsets.len();
if shader_group_size > 0 {
assert!(cxview.draw_calls_len >= shader_group_size);
for index in cxview.draw_calls_len - shader_group_size..cxview.draw_calls_len {
if cxview.draw_calls[index].shader_id == shader_id {
return &mut cxview.draw_calls[index];
}
}
panic!("Trying to use Shader within a shader group that isn't part of the group");
} else {
if cxview.draw_calls_len > 0 && !self.debug_flags.disable_draw_call_batching {
let dc = &mut cxview.draw_calls[cxview.draw_calls_len - 1];
if dc.props.is_batchable() && dc.sub_view_id == 0 && dc.shader_id == shader_id {
return &mut cxview.draw_calls[cxview.draw_calls_len - 1];
}
}
}
}
cxview.draw_calls_len += 1;
if draw_call_id >= cxview.draw_calls.len() {
cxview.draw_calls.push(DrawCall {
props,
draw_call_id,
view_id: current_view_id,
redraw_id: self.redraw_id,
sub_view_id: 0,
shader_id,
instances: Vec::new(),
draw_uniforms: DrawUniforms::default(),
user_uniforms: {
let mut f = Vec::new();
f.resize(sh.mapping.user_uniform_props.total_slots, 0.0);
f
},
textures_2d: {
let mut f = Vec::new();
f.resize(sh.mapping.textures.len(), 0);
f
},
instance_dirty: true,
uniforms_dirty: true,
platform: CxPlatformDrawCall::default(),
});
let dc = &mut cxview.draw_calls[draw_call_id];
return dc;
}
let dc = &mut cxview.draw_calls[draw_call_id];
dc.shader_id = shader_id;
dc.props = props;
dc.sub_view_id = 0; dc.redraw_id = self.redraw_id;
dc.instances.truncate(0);
dc.user_uniforms.truncate(0);
dc.user_uniforms.resize(sh.mapping.user_uniform_props.total_slots, 0.0);
dc.textures_2d.truncate(0);
dc.textures_2d.resize(sh.mapping.textures.len(), 0);
dc.instance_dirty = true;
dc.uniforms_dirty = true;
dc
}
fn add_instances_internal<T: Sized>(&mut self, shader: &'static Shader, data: &[T], props: DrawCallProps) -> Area {
if data.is_empty() {
return Area::Empty;
}
let shader_id = self.get_shader_id(shader);
let total_instance_slots = self.shaders[shader_id].mapping.instance_props.total_slots;
assert_eq!(total_instance_slots * std::mem::size_of::<f32>(), std::mem::size_of::<T>());
let dc = self.create_draw_call(shader_id, props);
let ia = InstanceRangeArea {
view_id: dc.view_id,
draw_call_id: dc.draw_call_id,
instance_count: data.len(),
instance_offset: dc.instances.len(),
redraw_id: dc.redraw_id,
};
dc.instances.extend_from_slice(data.as_f32_slice());
let area = Area::InstanceRange(ia);
self.add_to_box_align_list(area);
area
}
pub fn add_instances<T: Sized>(&mut self, shader: &'static Shader, data: &[T]) -> Area {
self.add_instances_internal(shader, data, DrawCallProps::default())
}
pub fn add_mesh_instances<T: Sized>(&mut self, shader: &'static Shader, data: &[T], gpu_geometry: GpuGeometry) -> Area {
assert!(self.shader_group_instance_offsets.is_empty(), "Can't add mesh instances when in a shader group");
self.add_instances_internal(shader, data, DrawCallProps { gpu_geometry: Some(gpu_geometry), ..Default::default() })
}
pub fn add_instances_with_scroll_sticky<T: Sized>(
&mut self,
shader: &'static Shader,
data: &[T],
horizontal: bool,
vertical: bool,
) -> Area {
assert!(self.shader_group_instance_offsets.is_empty(), "Can't add instances with scroll sticky when in a shader group");
self.add_instances_internal(
shader,
data,
DrawCallProps { scroll_sticky_horizontal: horizontal, scroll_sticky_vertical: vertical, ..Default::default() },
)
}
pub fn begin_shader_group(&mut self, shaders_ordered: &[&'static Shader]) {
assert!(self.in_redraw_cycle, "Must be in redraw cycle to call begin_shader_group");
assert!(self.shader_group_instance_offsets.is_empty(), "Nested shader groups are not supported (yet)");
let shader_ids: Vec<usize> = shaders_ordered.iter().map(|&shader| self.get_shader_id(shader)).collect();
debug_assert!(
{
let mut unique_shader_ids = shader_ids.clone();
unique_shader_ids.sort_unstable();
unique_shader_ids.dedup();
shader_ids.len() == unique_shader_ids.len()
},
"Can't use shader more than once in shader group"
);
let shader_group_size = shaders_ordered.len();
let current_view_id = *self.view_stack.last().unwrap();
let cxview = &self.views[current_view_id];
if self.debug_flags.disable_draw_call_batching
|| cxview.draw_calls_len < shader_group_size
|| shader_ids.iter().enumerate().any(|(index, &shader_id)| {
let dc = &cxview.draw_calls[cxview.draw_calls_len - shader_group_size + index];
dc.shader_id != shader_id || dc.sub_view_id != 0
})
{
for shader_id in shader_ids {
self.create_draw_call(shader_id, DrawCallProps::default());
}
}
let cxview = &self.views[current_view_id];
self.shader_group_instance_offsets.extend(
(cxview.draw_calls_len - shader_group_size..cxview.draw_calls_len)
.map(|index| cxview.draw_calls[index].instances.len()),
);
}
pub fn end_shader_group(&mut self) {
assert!(!self.shader_group_instance_offsets.is_empty(), "Call begin_shader_group before end_shader_group");
self.shader_group_instance_offsets.clear();
}
pub fn set_view_scroll_x(&mut self, view_id: usize, scroll_pos: f32) {
let fac = self.get_delegated_dpi_factor(self.views[view_id].pass_id);
let cxview = &mut self.views[view_id];
cxview.unsnapped_scroll.x = scroll_pos;
let snapped = scroll_pos - scroll_pos % (1.0 / fac);
if cxview.snapped_scroll.x != snapped {
cxview.snapped_scroll.x = snapped;
self.passes[cxview.pass_id].paint_dirty = true;
}
}
pub fn set_view_scroll_y(&mut self, view_id: usize, scroll_pos: f32) {
let fac = self.get_delegated_dpi_factor(self.views[view_id].pass_id);
let cxview = &mut self.views[view_id];
cxview.unsnapped_scroll.y = scroll_pos;
let snapped = scroll_pos - scroll_pos % (1.0 / fac);
if cxview.snapped_scroll.y != snapped {
cxview.snapped_scroll.y = snapped;
self.passes[cxview.pass_id].paint_dirty = true;
}
}
}
#[derive(Default, Clone)]
#[repr(C, align(8))]
pub(crate) struct DrawUniforms {
draw_clip_x1: f32,
draw_clip_y1: f32,
draw_clip_x2: f32,
draw_clip_y2: f32,
pub(crate) draw_scroll_x: f32,
pub(crate) draw_scroll_y: f32,
pub(crate) draw_local_scroll_x: f32,
pub(crate) draw_local_scroll_y: f32,
draw_zbias: f32,
}
impl DrawUniforms {
pub fn as_slice(&self) -> &[f32; std::mem::size_of::<DrawUniforms>()] {
unsafe { std::mem::transmute(self) }
}
}
#[derive(Default)]
pub(crate) struct DrawCallProps {
pub(crate) gpu_geometry: Option<GpuGeometry>,
scroll_sticky_vertical: bool,
scroll_sticky_horizontal: bool,
}
impl DrawCallProps {
fn is_batchable(&self) -> bool {
self.gpu_geometry.is_none() && !self.scroll_sticky_horizontal && !self.scroll_sticky_vertical
}
}
#[derive(Default)]
pub struct DrawCall {
pub(crate) draw_call_id: usize,
pub(crate) view_id: usize,
pub(crate) redraw_id: u64,
pub(crate) sub_view_id: usize,
pub(crate) shader_id: usize,
pub(crate) instances: Vec<f32>,
pub(crate) user_uniforms: Vec<f32>,
pub(crate) textures_2d: Vec<u32>,
pub(crate) instance_dirty: bool,
pub(crate) uniforms_dirty: bool,
pub(crate) draw_uniforms: DrawUniforms,
pub(crate) platform: CxPlatformDrawCall,
pub(crate) props: DrawCallProps,
}
impl DrawCall {
pub(crate) fn set_local_scroll(&mut self, scroll: Vec2, local_scroll: Vec2) {
self.draw_uniforms.draw_scroll_x = scroll.x;
if !self.props.scroll_sticky_horizontal {
self.draw_uniforms.draw_scroll_x += local_scroll.x;
}
self.draw_uniforms.draw_scroll_y = scroll.y;
if !self.props.scroll_sticky_vertical {
self.draw_uniforms.draw_scroll_y += local_scroll.y;
}
self.draw_uniforms.draw_local_scroll_x = local_scroll.x;
self.draw_uniforms.draw_local_scroll_y = local_scroll.y;
}
pub(crate) fn set_zbias(&mut self, zbias: f32) {
self.draw_uniforms.draw_zbias = zbias;
}
pub(crate) fn set_clip(&mut self, clip: (Vec2, Vec2)) {
self.draw_uniforms.draw_clip_x1 = clip.0.x;
self.draw_uniforms.draw_clip_y1 = clip.0.y;
self.draw_uniforms.draw_clip_x2 = clip.1.x;
self.draw_uniforms.draw_clip_y2 = clip.1.y;
}
pub(crate) fn clip_and_scroll_rect(&self, x: f32, y: f32, w: f32, h: f32) -> Rect {
let mut x1 = x - self.draw_uniforms.draw_scroll_x;
let mut y1 = y - self.draw_uniforms.draw_scroll_y;
let mut x2 = x1 + w;
let mut y2 = y1 + h;
x1 = self.draw_uniforms.draw_clip_x1.max(x1).min(self.draw_uniforms.draw_clip_x2);
y1 = self.draw_uniforms.draw_clip_y1.max(y1).min(self.draw_uniforms.draw_clip_y2);
x2 = self.draw_uniforms.draw_clip_x1.max(x2).min(self.draw_uniforms.draw_clip_x2);
y2 = self.draw_uniforms.draw_clip_y1.max(y2).min(self.draw_uniforms.draw_clip_y2);
Rect { pos: vec2(x1, y1), size: vec2(x2 - x1, y2 - y1) }
}
}
#[derive(Default, Clone)]
#[repr(C)]
pub struct ViewUniforms {}
impl ViewUniforms {
pub fn as_slice(&self) -> &[f32; std::mem::size_of::<ViewUniforms>()] {
unsafe { std::mem::transmute(self) }
}
}
#[derive(Default)]
pub struct CxView {
pub(crate) draw_calls: Vec<DrawCall>,
pub(crate) rect: Rect,
pub(crate) redraw_id: u64,
pub(crate) pass_id: usize,
pub(crate) draw_calls_len: usize,
pub(crate) parent_scroll: Vec2,
pub(crate) view_uniforms: ViewUniforms,
pub(crate) unsnapped_scroll: Vec2,
pub(crate) snapped_scroll: Vec2,
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(crate) platform: CxPlatformView,
}
impl CxView {
pub(crate) fn intersect_clip(&self, clip: (Vec2, Vec2)) -> (Vec2, Vec2) {
let min_x = self.rect.pos.x - self.parent_scroll.x;
let min_y = self.rect.pos.y - self.parent_scroll.y;
let max_x = self.rect.pos.x + self.rect.size.x - self.parent_scroll.x;
let max_y = self.rect.pos.y + self.rect.size.y - self.parent_scroll.y;
(Vec2 { x: min_x.max(clip.0.x), y: min_y.max(clip.0.y) }, Vec2 { x: max_x.min(clip.1.x), y: max_y.min(clip.1.y) })
}
}