Skip to content
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

Scroll box widget #96

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0150a98
bevy_asset_browser 1st iteration
hcabel Oct 12, 2024
a9745a9
Add embeded icons to the asset_browser
hcabel Oct 13, 2024
5686b1e
Fix all workflow error (except 1)
hcabel Oct 13, 2024
8652857
Add asset_browser svgs icon
hcabel Oct 14, 2024
c32ce5b
Use FileAssetReader base_paht for the asset browser location
hcabel Oct 15, 2024
36ca7b3
Assetbrowser rewrite to use FileAssetReader instead of std::fs
hcabel Oct 17, 2024
8b520b5
bevy_asset_browser 1st iteration
hcabel Oct 12, 2024
7c98284
Add embeded icons to the asset_browser
hcabel Oct 13, 2024
181a751
Fix all workflow error (except 1)
hcabel Oct 13, 2024
88eb3b5
Add asset_browser svgs icon
hcabel Oct 14, 2024
e4952e8
Use FileAssetReader base_paht for the asset browser location
hcabel Oct 15, 2024
6e354f3
Assetbrowser rewrite to use FileAssetReader instead of std::fs
hcabel Oct 17, 2024
26f0ca9
Merge branch 'asset_browser' of github.com:hcabel/bevy_editor_prototy…
hcabel Oct 17, 2024
c97c00b
Reflect latest `bevy` update
hcabel Oct 17, 2024
44b506c
Use pane layout for the AssetBrowser
hcabel Oct 17, 2024
71a27ca
Add AssetBrowser Pane to editor UI
hcabel Oct 17, 2024
467ae92
Remove todo
hcabel Oct 17, 2024
ff3f850
Remove unused import
hcabel Oct 17, 2024
9c3b990
Change mouse cursor when hovering asset browsers buttons
hcabel Oct 17, 2024
1525bea
Merge branch 'bevyengine:main' into asset_browser
hcabel Oct 18, 2024
3f7f787
Upgrade Node/Image/Button bundles
hcabel Oct 18, 2024
f2fa33e
Add Horizontal resize handle
hcabel Oct 18, 2024
db64295
Fix clippy errors
hcabel Oct 18, 2024
0e1590d
Add a scroll box widget
hcabel Oct 19, 2024
714113a
Use scroll box widget in Asset browser pane
hcabel Oct 19, 2024
0ee9c92
Merge branch 'main' into asset_browser
hcabel Oct 19, 2024
aaa193b
Merge branch 'asset_browser' into scroll_box
hcabel Oct 19, 2024
ecc244f
Reflect latest `bevy` commit changes
hcabel Oct 19, 2024
025c9fb
Merge branch 'asset_browser' into scroll_box
hcabel Oct 19, 2024
c18b2c7
Reflect latest `bevy` commit changes
hcabel Oct 19, 2024
28c73d2
spawn_scroll_box return scroll_box_container EntityCommand
hcabel Oct 19, 2024
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ bevy_command_palette = { path = "bevy_widgets/bevy_command_palette" }
bevy_context_menu = { path = "bevy_widgets/bevy_context_menu" }
bevy_i-cant-believe-its-not-bsn = { path = "bevy_widgets/bevy_i-cant-believe-its-not-bsn" }
bevy_menu_bar = { path = "bevy_widgets/bevy_menu_bar" }
bevy_scroll_box = { path = "bevy_widgets/bevy_scroll_box" }
bevy_toolbar = { path = "bevy_widgets/bevy_toolbar" }
bevy_tooltips = { path = "bevy_widgets/bevy_tooltips" }

Expand Down
4 changes: 4 additions & 0 deletions bevy_editor_panes/bevy_asset_browser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ edition = "2021"

[dependencies]
bevy.workspace = true
bevy_editor_styles.workspace = true
bevy_pane_layout.workspace = true
bevy_scroll_box.workspace = true
atomicow = "1.0.0"

[lints]
workspace = true
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions bevy_editor_panes/bevy_asset_browser/src/assets/file_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions bevy_editor_panes/bevy_asset_browser/src/assets/source_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
228 changes: 228 additions & 0 deletions bevy_editor_panes/bevy_asset_browser/src/directory_content.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
use bevy::{
asset::io::AssetSourceBuilders,
prelude::*,
tasks::{
block_on,
futures_lite::{future, StreamExt},
IoTaskPool, Task,
},
window::SystemCursorIcon,
winit::cursor::CursorIcon,
};
use bevy_editor_styles::Theme;
use bevy_scroll_box::ScrollBox;

use crate::{AssetBrowserLocation, AssetType};

/// The root node for the directory content view
#[derive(Component)]
#[require(Node)]
pub struct DirectoryContentNode;

/// One entry of [`DirectoryContent`]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AssetEntry {
pub name: String,
pub asset_type: AssetType,
}

#[derive(Resource, Default, Debug, Clone, PartialEq, Eq)]
pub struct DirectoryContent(pub Vec<AssetEntry>);

#[derive(Component)]
/// The task that fetches the content of current [`AssetBrowserLocation`]
pub(crate) struct FetchDirectoryContentTask(Task<DirectoryContent>);

pub(crate) fn run_if_fetch_task_is_running(
task_query: Query<(Entity, &FetchDirectoryContentTask)>,
) -> bool {
task_query.iter().count() > 0
}

/// Poll the [`FetchDirectoryContentTask`] to check if it's done
/// If it's done, despawn the task entity and insert the result into [`DirectoryContent`]
pub(crate) fn poll_fetch_task(
mut commands: Commands,
mut task_query: Query<(Entity, &mut FetchDirectoryContentTask)>,
) {
let (task_entity, mut task) = task_query.single_mut();
if let Some(content) = block_on(future::poll_once(&mut task.0)) {
commands.entity(task_entity).despawn();
commands.insert_resource(content);
}
}

/// Spawn a new IO [`FetchDirectoryContentTask`] to fetch the content of the current [`AssetBrowserLocation`]
pub fn fetch_directory_content(
mut commands: Commands,
mut asset_source_builder: ResMut<AssetSourceBuilders>,
location: Res<AssetBrowserLocation>,
) {
let sources = asset_source_builder.build_sources(false, false);
if location.source_id.is_none() {
commands.insert_resource(DirectoryContent(
sources
.iter()
.map(|source| AssetEntry {
name: crate::asset_source_id_to_string(&source.id()),
asset_type: AssetType::EngineSource,
})
.collect(),
));
return;
}
let location = location.clone();
let task = IoTaskPool::get().spawn(async move {
let source = sources.get(location.source_id.unwrap()).unwrap();
let reader = source.reader();
let mut dir_stream = reader
.read_directory(location.path.as_path())
.await
.unwrap();
let mut content = DirectoryContent::default();
while let Some(entry) = dir_stream.next().await {
let asset_type = if reader.is_directory(&entry).await.unwrap() {
AssetType::Directory
} else {
AssetType::Unknown
};
content.0.push(AssetEntry {
name: entry
.components()
.last()
.unwrap()
.as_os_str()
.to_string_lossy()
.to_string(),
asset_type,
});
}
content
});

commands
.spawn_empty()
.insert(FetchDirectoryContentTask(task));
}

/// Check if the [`DirectoryContent`] has changed, which relate to the content of the current [`AssetBrowserLocation`]
pub(crate) fn run_if_content_as_changed(directory_content: Res<DirectoryContent>) -> bool {
directory_content.is_changed()
}

/// Refresh the UI with the content of the current [`AssetBrowserLocation`]
pub(crate) fn refresh_ui(
mut commands: Commands,
content_list_query: Query<(Entity, Option<&Children>), With<ScrollBox>>,
theme: Res<Theme>,
asset_server: Res<AssetServer>,
directory_content: Res<DirectoryContent>,
location: Res<AssetBrowserLocation>,
mut asset_sources_builder: ResMut<AssetSourceBuilders>,
) {
let (content_list_entity, content_list_children) = content_list_query.single();
if let Some(content_list_children) = content_list_children {
for child in content_list_children {
commands.entity(*child).despawn_recursive();
}
commands
.entity(content_list_entity)
.remove_children(content_list_children);
}
commands
.entity(content_list_entity)
.with_children(|parent| {
if location.source_id.is_none() {
let sources = asset_sources_builder.build_sources(false, false);
sources.iter().for_each(|source| {
spawn_asset_button(
parent,
AssetType::EngineSource,
crate::asset_source_id_to_string(&source.id()),
&theme,
&asset_server,
);
});
} else {
for entry in &directory_content.0 {
spawn_asset_button(
parent,
entry.asset_type,
entry.name.clone(),
&theme,
&asset_server,
);
}
}
});
}

/// Spawn a new asset button UI element
fn spawn_asset_button(
parent: &mut ChildBuilder,
asset_type: AssetType,
name: String,
theme: &Res<Theme>,
asset_server: &Res<AssetServer>,
) {
let mut entity_commands = parent.spawn((
Button,
Node {
margin: UiRect::all(Val::Px(5.0)),
padding: UiRect::all(Val::Px(5.0)),
height: Val::Px(100.0),
width: Val::Px(100.0),
align_items: AlignItems::Center,
flex_direction: FlexDirection::Column,
border: UiRect::all(Val::Px(3.0)),
justify_content: JustifyContent::SpaceBetween,
..default()
},
theme.border_radius,
crate::ButtonType::AssetButton(asset_type),
));
entity_commands.with_children(|parent| {
parent.spawn((
UiImage::new(crate::content_button_to_icon(&asset_type, asset_server)),
Node {
height: Val::Px(50.0),
..default()
},
));
parent.spawn((
Text::new(name),
TextFont {
font_size: 12.0,
..default()
},
TextColor(theme.text_color),
));
});

match asset_type {
AssetType::Directory | AssetType::EngineSource => {
entity_commands
.observe(
move |_trigger: Trigger<Pointer<Move>>,
window_query: Query<Entity, With<Window>>,
mut commands: Commands| {
let window = window_query.single();
commands
.entity(window)
.insert(CursorIcon::System(SystemCursorIcon::Pointer));
},
)
.observe(
move |_trigger: Trigger<Pointer<Out>>,
window_query: Query<Entity, With<Window>>,
mut commands: Commands| {
let window = window_query.single();
commands
.entity(window)
.insert(CursorIcon::System(SystemCursorIcon::Default));
},
);
}
_ => {}
}
}
Loading
Loading