#![warn(missing_docs)]
use crate::{
border::BorderBuilder,
brush::Brush,
core::{
color::Color, pool::Handle, reflect::prelude::*, type_traits::prelude::*, uuid_provider,
variable::InheritableVariable, visitor::prelude::*,
},
decorator::{Decorator, DecoratorMessage},
draw::{CommandTexture, Draw, DrawingContext},
message::{KeyCode, UiMessage},
scroll_viewer::{ScrollViewer, ScrollViewerBuilder, ScrollViewerMessage},
stack_panel::StackPanelBuilder,
style::{resource::StyleResourceExt, Style},
widget::{Widget, WidgetBuilder, WidgetMessage},
BuildContext, Control, Thickness, UiNode, UserInterface,
};
use fyrox_core::pool::{HandlesVecExtension, ObjectOrVariant};
use crate::message::MessageData;
use fyrox_graph::{
constructor::{ConstructorProvider, GraphNodeConstructor},
SceneGraph,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListViewMessage {
Selection(Vec<usize>),
Items(Vec<Handle<UiNode>>),
AddItem(Handle<UiNode>),
RemoveItem(Handle<UiNode>),
BringItemIntoView(Handle<UiNode>),
}
impl MessageData for ListViewMessage {}
impl ListViewMessage {
pub fn add_item(handle: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
Self::AddItem(handle.to_base())
}
pub fn remove_item(handle: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
Self::RemoveItem(handle.to_base())
}
pub fn bring_item_into_view(handle: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
Self::BringItemIntoView(handle.to_base())
}
}
#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
#[visit(optional)]
#[reflect(derived_type = "UiNode")]
pub struct ListView {
pub widget: Widget,
pub selection: Vec<usize>,
pub item_containers: InheritableVariable<Vec<Handle<ListViewItem>>>,
pub panel: InheritableVariable<Handle<UiNode>>,
pub items: InheritableVariable<Vec<Handle<UiNode>>>,
pub scroll_viewer: InheritableVariable<Handle<ScrollViewer>>,
}
impl ConstructorProvider<UiNode, UserInterface> for ListView {
fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
GraphNodeConstructor::new::<Self>()
.with_variant("List View", |ui| {
ListViewBuilder::new(WidgetBuilder::new().with_name("List View"))
.build(&mut ui.build_ctx())
.to_base()
.into()
})
.with_group("Input")
}
}
crate::define_widget_deref!(ListView);
impl ListView {
pub fn items(&self) -> &[Handle<UiNode>] {
&self.items
}
fn fix_selection(&self, ui: &UserInterface) {
let mut fixed_selection = Vec::with_capacity(self.selection.len());
for &selected_index in self.selection.iter() {
if selected_index >= self.items.len() {
if !self.items.is_empty() {
fixed_selection.push(self.items.len() - 1);
}
} else {
fixed_selection.push(selected_index);
}
}
if self.selection != fixed_selection {
ui.send(self.handle, ListViewMessage::Selection(fixed_selection));
}
}
fn largest_selection_index(&self) -> Option<usize> {
self.selection.iter().max().cloned()
}
fn smallest_selection_index(&self) -> Option<usize> {
self.selection.iter().min().cloned()
}
fn sync_decorators(&self, ui: &UserInterface) {
for (i, &container) in self.item_containers.iter().enumerate() {
let select = self.selection.contains(&i);
if let Ok(container) = ui.try_get(container) {
let mut stack = container.children().to_vec();
while let Some(handle) = stack.pop() {
let node = ui.node(handle);
if node.cast::<ListView>().is_some() {
} else if node.cast::<Decorator>().is_some() {
ui.send(handle, DecoratorMessage::Select(select));
} else {
stack.extend_from_slice(node.children())
}
}
}
}
}
}
#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
#[reflect(derived_type = "UiNode")]
pub struct ListViewItem {
pub widget: Widget,
}
impl ConstructorProvider<UiNode, UserInterface> for ListViewItem {
fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
GraphNodeConstructor::new::<Self>()
.with_variant("List View Item", |ui: &mut UserInterface| {
let node = UiNode::new(ListViewItem {
widget: WidgetBuilder::new()
.with_name("List View Item")
.build(&ui.build_ctx()),
});
ui.add_node(node).into()
})
.with_group("Input")
}
}
crate::define_widget_deref!(ListViewItem);
uuid_provider!(ListViewItem = "02f21415-5843-42f5-a3e4-b4a21e7739ad");
impl Control for ListViewItem {
fn draw(&self, drawing_context: &mut DrawingContext) {
drawing_context.push_rect_filled(&self.widget.bounding_rect(), None);
drawing_context.commit(
self.clip_bounds(),
Brush::Solid(Color::TRANSPARENT),
CommandTexture::None,
&self.material,
None,
);
}
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.widget.handle_routed_message(ui, message);
let parent_list_view =
self.find_by_criteria_up(ui, |node| node.cast::<ListView>().is_some());
if let Some(WidgetMessage::MouseUp { .. }) = message.data::<WidgetMessage>() {
if !message.handled() {
let list_view = ui
.node(parent_list_view)
.cast::<ListView>()
.expect("Parent of ListViewItem must be ListView!");
let self_index = list_view
.item_containers
.iter()
.position(|c| self.handle == *c)
.expect("ListViewItem must be used as a child of ListView");
let new_selection = if ui.keyboard_modifiers.control {
let mut selection = list_view.selection.clone();
selection.push(self_index);
selection
} else {
vec![self_index]
};
ui.send(parent_list_view, ListViewMessage::Selection(new_selection));
message.set_handled(true);
}
}
}
}
uuid_provider!(ListView = "5832a643-5bf9-4d84-8358-b4c45bb440e8");
impl Control for ListView {
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.widget.handle_routed_message(ui, message);
if let Some(msg) = message.data_for::<ListViewMessage>(self.handle()) {
match msg {
ListViewMessage::Items(items) => {
let item_containers = generate_item_containers(&mut ui.build_ctx(), items);
ui.send(
*self.panel,
WidgetMessage::ReplaceChildren(item_containers.clone().to_base()),
);
self.item_containers
.set_value_and_mark_modified(item_containers);
self.items.set_value_and_mark_modified(items.clone());
self.fix_selection(ui);
self.sync_decorators(ui);
}
&ListViewMessage::AddItem(item) => {
let item_container = generate_item_container(&mut ui.build_ctx(), item);
ui.send(item_container, WidgetMessage::LinkWith(*self.panel));
self.item_containers.push(item_container);
self.items.push(item);
}
ListViewMessage::Selection(selection) => {
if &self.selection != selection {
self.selection.clone_from(selection);
self.sync_decorators(ui);
ui.try_send_response(message);
}
}
&ListViewMessage::RemoveItem(item) => {
if let Some(item_position) = self.items.iter().position(|i| *i == item) {
self.items.remove(item_position);
self.item_containers.remove(item_position);
let container = ui.node(item).parent();
ui.send(container, WidgetMessage::Remove);
self.fix_selection(ui);
self.sync_decorators(ui);
}
}
&ListViewMessage::BringItemIntoView(item) => {
if self.items.contains(&item) {
ui.send(
*self.scroll_viewer,
ScrollViewerMessage::BringIntoView(item),
);
}
}
}
} else if let Some(WidgetMessage::KeyDown(key_code)) = message.data() {
if !message.handled() {
let new_selection = if *key_code == KeyCode::ArrowDown {
match self.largest_selection_index() {
Some(i) => Some(i.saturating_add(1) % self.items.len()),
None => {
if self.items.is_empty() {
None
} else {
Some(0)
}
}
}
} else if *key_code == KeyCode::ArrowUp {
match self.smallest_selection_index() {
Some(i) => {
let mut index = (i as isize).saturating_sub(1);
let count = self.items.len() as isize;
if index < 0 {
index += count;
}
Some((index % count) as usize)
}
None => {
if self.items.is_empty() {
None
} else {
Some(0)
}
}
}
} else {
None
};
if let Some(new_selection) = new_selection {
ui.send(self.handle, ListViewMessage::Selection(vec![new_selection]));
ui.send(
self.handle,
ListViewMessage::BringItemIntoView(self.items[new_selection]),
);
message.set_handled(true);
}
}
}
}
}
pub struct ListViewBuilder {
widget_builder: WidgetBuilder,
items: Vec<Handle<UiNode>>,
panel: Option<Handle<UiNode>>,
scroll_viewer: Option<Handle<ScrollViewer>>,
selection: Vec<usize>,
}
impl ListViewBuilder {
pub fn new(widget_builder: WidgetBuilder) -> Self {
Self {
widget_builder,
items: Vec::new(),
panel: None,
scroll_viewer: None,
selection: Default::default(),
}
}
pub fn with_items(mut self, items: Vec<Handle<UiNode>>) -> Self {
self.items = items;
self
}
pub fn with_items_panel(mut self, panel: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
self.panel = Some(panel.to_base());
self
}
pub fn with_scroll_viewer(mut self, sv: Handle<ScrollViewer>) -> Self {
self.scroll_viewer = Some(sv);
self
}
pub fn with_selection(mut self, items: Vec<usize>) -> Self {
self.selection = items;
self
}
pub fn build(self, ctx: &mut BuildContext) -> Handle<ListView> {
let item_containers = generate_item_containers(ctx, &self.items);
for (i, &container) in item_containers.iter().enumerate() {
let select = self.selection.contains(&i);
if let Ok(container) = ctx.inner().try_get(container) {
let mut stack = container.children().to_vec();
while let Some(handle) = stack.pop() {
let node = &mut ctx[handle];
if node.cast::<ListView>().is_some() {
} else if let Some(decorator) = node.cast_mut::<Decorator>() {
decorator.is_selected.set_value_and_mark_modified(select);
if select {
decorator.background = (*decorator.selected_brush).clone().into();
} else {
decorator.background = (*decorator.normal_brush).clone().into();
}
} else {
stack.extend_from_slice(node.children())
}
}
}
}
let panel = self.panel.unwrap_or_else(|| {
StackPanelBuilder::new(WidgetBuilder::new())
.build(ctx)
.to_base()
});
for &item_container in item_containers.iter() {
ctx.link(item_container, panel);
}
let style = &ctx.style;
let back = BorderBuilder::new(
WidgetBuilder::new()
.with_background(style.property(Style::BRUSH_DARK))
.with_foreground(style.property(Style::BRUSH_LIGHT)),
)
.with_stroke_thickness(Thickness::uniform(1.0).into())
.build(ctx);
let scroll_viewer = self.scroll_viewer.unwrap_or_else(|| {
ScrollViewerBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(0.0)))
.build(ctx)
});
let scroll_viewer_ref = &mut ctx[scroll_viewer];
scroll_viewer_ref.content = panel;
let content_presenter = scroll_viewer_ref.scroll_panel;
ctx.link(panel, content_presenter);
ctx.link(scroll_viewer, back);
let list_box = ListView {
widget: self
.widget_builder
.with_accepts_input(true)
.with_child(back)
.build(ctx),
selection: self.selection,
item_containers: item_containers.into(),
items: self.items.into(),
panel: panel.into(),
scroll_viewer: scroll_viewer.into(),
};
ctx.add(list_box)
}
}
fn generate_item_container(ctx: &mut BuildContext, item: Handle<UiNode>) -> Handle<ListViewItem> {
let item = ListViewItem {
widget: WidgetBuilder::new().with_child(item).build(ctx),
};
ctx.add(item)
}
fn generate_item_containers(
ctx: &mut BuildContext,
items: &[Handle<UiNode>],
) -> Vec<Handle<ListViewItem>> {
items
.iter()
.map(|&item| generate_item_container(ctx, item))
.collect()
}
#[cfg(test)]
mod test {
use crate::list_view::ListViewBuilder;
use crate::{test::test_widget_deletion, widget::WidgetBuilder};
#[test]
fn test_deletion() {
test_widget_deletion(|ctx| ListViewBuilder::new(WidgetBuilder::new()).build(ctx));
}
}