pub use self::conrod_core::event::Input;
pub use self::conrod_core::{
color, cursor, event, graph, image, input, position, scroll, text, theme, utils, widget,
widget_ids,
};
pub use self::conrod_core::{
Borderable, Bordering, Color, Colorable, Dimensions, FontSize, Labelable, Point, Positionable,
Range, Rect, Scalar, Sizeable, Theme, UiCell, Widget,
};
pub use crate::conrod_core;
pub use crate::conrod_wgpu;
pub use crate::conrod_winit;
pub mod prelude {
pub use super::{Borderable, Colorable, Labelable, Positionable, Sizeable, Widget};
pub use super::{
Bordering, Dimensions, FontSize, Input, Point, Range, Rect, Scalar, Theme, Ui, UiCell,
};
pub use super::{color, image, position, text, widget, widget_ids};
}
use self::conrod_core::text::rt::gpu_cache::CacheWriteErr;
use crate::frame::Frame;
use crate::text::{font, Font};
use crate::window::{self, Window};
use crate::{wgpu, App};
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::{mpsc, Arc, Mutex};
use winit;
pub(crate) struct Arrangement {
pub(super) windows: RefCell<HashMap<window::Id, Vec<Handle>>>,
}
pub(crate) struct Handle {
pub(crate) input_tx: Option<mpsc::SyncSender<Input>>,
}
pub struct Ui {
window_id: window::Id,
ui: conrod_core::Ui,
input_rx: Option<mpsc::Receiver<Input>>,
pub image_map: ImageMap,
renderer: Mutex<conrod_wgpu::Renderer>,
}
pub struct Builder<'a> {
app: &'a App,
window_id: Option<window::Id>,
dimensions: Option<[Scalar; 2]>,
theme: Option<Theme>,
automatically_handle_input: bool,
pending_input_limit: usize,
default_font_path: Option<PathBuf>,
glyph_cache_dimensions: Option<(u32, u32)>,
}
#[derive(Debug)]
pub enum BuildError {
InvalidWindow,
FailedToLoadFont(text::font::Error),
}
#[derive(Debug)]
pub enum DrawToFrameError {
InvalidWindow,
RendererPoisoned,
RenderModePoisoned,
InvalidRenderMode,
RendererFill(CacheWriteErr),
}
pub type ImageMap = conrod_core::image::Map<conrod_wgpu::Image>;
impl Arrangement {
pub(crate) fn new() -> Self {
let windows = RefCell::new(HashMap::new());
Arrangement { windows }
}
}
impl<'a> Builder<'a> {
pub(super) fn new(app: &'a App) -> Self {
Builder {
app,
window_id: None,
dimensions: None,
theme: None,
automatically_handle_input: true,
pending_input_limit: Ui::DEFAULT_PENDING_INPUT_LIMIT,
default_font_path: None,
glyph_cache_dimensions: None,
}
}
pub fn window(mut self, window_id: window::Id) -> Self {
self.window_id = Some(window_id);
self
}
pub fn with_dimensions(mut self, dimensions: [Scalar; 2]) -> Self {
self.dimensions = Some(dimensions);
self
}
pub fn with_theme(mut self, theme: Theme) -> Self {
self.theme = Some(theme);
self
}
pub fn automatically_handle_input(mut self, b: bool) -> Self {
self.automatically_handle_input = b;
self
}
pub fn pending_input_limit(mut self, limit: usize) -> Self {
self.pending_input_limit = limit;
self
}
pub fn default_font_path(mut self, path: PathBuf) -> Self {
self.default_font_path = Some(path);
self
}
pub fn with_glyph_cache_dimensions(mut self, width: u32, height: u32) -> Self {
self.glyph_cache_dimensions = Some((width, height));
self
}
pub fn build(self) -> Result<Ui, BuildError> {
let Builder {
app,
window_id,
dimensions,
theme,
pending_input_limit,
automatically_handle_input,
default_font_path,
glyph_cache_dimensions,
} = self;
let window_id = window_id.unwrap_or(app.window_id());
let window = app.window(window_id).ok_or(BuildError::InvalidWindow)?;
let msaa_samples = window.msaa_samples();
let dimensions = dimensions.unwrap_or_else(|| {
let (win_w, win_h) = window.inner_size_points();
[win_w as Scalar, win_h as Scalar]
});
let theme = theme.unwrap_or_else(Theme::default);
let ui = conrod_core::UiBuilder::new(dimensions).theme(theme).build();
let (input_rx, handle) = if automatically_handle_input {
let (input_tx, input_rx) = mpsc::sync_channel(pending_input_limit);
let input_tx = Some(input_tx);
let input_rx = Some(input_rx);
let handle = Handle { input_tx };
(input_rx, handle)
} else {
let input_tx = None;
let input_rx = None;
let handle = Handle { input_tx };
(input_rx, handle)
};
app.ui
.windows
.borrow_mut()
.entry(window_id)
.or_insert(Vec::new())
.push(handle);
let device = window.swap_chain_device().clone();
let texture_format = Frame::TEXTURE_FORMAT;
let renderer = match glyph_cache_dimensions {
Some((w, h)) => {
let dims = [w as _, h as _];
conrod_wgpu::Renderer::with_glyph_cache_dimensions(
device,
msaa_samples,
texture_format,
dims,
)
}
None => conrod_wgpu::Renderer::new(device, msaa_samples, texture_format),
};
let renderer = Mutex::new(renderer);
let image_map = image::Map::new();
let mut ui = Ui {
window_id,
ui,
input_rx,
image_map,
renderer,
};
let default_font = default_font(default_font_path.as_ref().map(|path| path.as_path()))?;
ui.fonts_mut().insert(default_font);
Ok(ui)
}
}
impl Ui {
pub const DEFAULT_PENDING_INPUT_LIMIT: usize = 1024;
pub fn generate_widget_id(&mut self) -> widget::Id {
self.widget_id_generator().next()
}
pub fn widget_id_generator(&mut self) -> widget::id::Generator {
self.ui.widget_id_generator()
}
pub fn handle_input(&mut self, input: Input) {
self.ui.handle_event(input)
}
pub fn handle_pending_input(&mut self) {
let Ui {
ref mut ui,
ref input_rx,
..
} = *self;
if let Some(ref rx) = *input_rx {
for input in rx.try_iter() {
ui.handle_event(input);
}
}
}
pub fn set_widgets(&mut self) -> UiCell {
self.handle_pending_input();
self.ui.set_widgets()
}
pub fn fonts_mut(&mut self) -> &mut text::font::Map {
&mut self.ui.fonts
}
pub fn theme_mut(&mut self) -> &mut Theme {
&mut self.ui.theme
}
pub fn clear_with(&mut self, color: Color) {
self.ui.clear_with(color)
}
pub fn draw_to_frame(&self, app: &App, frame: &Frame) -> Result<(), DrawToFrameError> {
let primitives = self.ui.draw();
let color_attachment_desc = frame.color_attachment_descriptor();
let mut command_encoder = frame.command_encoder();
let window = app
.window(self.window_id)
.ok_or(DrawToFrameError::InvalidWindow)?;
encode_render_pass(
self,
&*window,
primitives,
color_attachment_desc,
&mut *command_encoder,
)
}
pub fn draw_to_frame_if_changed(
&self,
app: &App,
frame: &Frame,
) -> Result<bool, DrawToFrameError> {
match self.ui.draw_if_changed() {
None => Ok(false),
Some(primitives) => {
let window = app
.window(self.window_id)
.ok_or(DrawToFrameError::InvalidWindow)?;
let color_attachment_desc = frame.color_attachment_descriptor();
let mut command_encoder = frame.command_encoder();
encode_render_pass(
self,
&*window,
primitives,
color_attachment_desc,
&mut *command_encoder,
)?;
Ok(true)
}
}
}
}
impl wgpu::Texture {
pub fn into_ui_image(self) -> conrod_wgpu::Image {
let texture_format = self.format();
let [width, height] = self.size();
let texture = Arc::try_unwrap(self.into_inner())
.expect("converting to UI image requires unique access to texture");
conrod_wgpu::Image {
texture,
texture_format,
width,
height,
}
}
}
pub fn encode_render_pass(
ui: &Ui,
window: &Window,
primitives: conrod_core::render::Primitives,
color_attachment_desc: wgpu::RenderPassColorAttachmentDescriptor,
encoder: &mut wgpu::CommandEncoder,
) -> Result<(), DrawToFrameError> {
let mut renderer = ui
.renderer
.lock()
.ok()
.ok_or(DrawToFrameError::RendererPoisoned)?;
let device = window.swap_chain_device();
let scale_factor = window.scale_factor();
let (win_w, win_h) = window.inner_size_pixels();
let viewport = [0.0, 0.0, win_w as f32, win_h as f32];
if let Some(cmd) = renderer
.fill(&ui.image_map, viewport, scale_factor as f64, primitives)
.unwrap()
{
cmd.load_buffer_and_encode(&device, encoder);
}
let render_pass_desc = wgpu::RenderPassDescriptor {
label: Some("nannou_ui_render_pass_descriptor"),
color_attachments: &[color_attachment_desc],
depth_stencil_attachment: None,
};
let render = renderer.render(&device, &ui.image_map);
{
let mut render_pass = encoder.begin_render_pass(&render_pass_desc);
render_pass.set_vertex_buffer(0, render.vertex_buffer.slice(..));
let instance_range = 0..1;
for cmd in render.commands {
match cmd {
conrod_wgpu::RenderPassCommand::SetPipeline { pipeline } => {
render_pass.set_pipeline(pipeline);
}
conrod_wgpu::RenderPassCommand::SetBindGroup { bind_group } => {
render_pass.set_bind_group(0, bind_group, &[]);
}
conrod_wgpu::RenderPassCommand::SetScissor {
top_left,
dimensions,
} => {
let [x, y] = top_left;
let [w, h] = dimensions;
render_pass.set_scissor_rect(x, y, w, h);
}
conrod_wgpu::RenderPassCommand::Draw { vertex_range } => {
render_pass.draw(vertex_range, instance_range.clone());
}
}
}
}
Ok(())
}
mod conrod_winit_conv {
conrod_winit::v023_conversion_fns!();
}
pub fn winit_window_event_to_input(
event: &winit::event::WindowEvent,
window: &Window,
) -> Option<Input> {
conrod_winit_conv::convert_window_event(event, &window.window)
}
impl Deref for Ui {
type Target = conrod_core::Ui;
fn deref(&self) -> &Self::Target {
&self.ui
}
}
impl From<text::font::Error> for BuildError {
fn from(err: text::font::Error) -> Self {
BuildError::FailedToLoadFont(err)
}
}
impl From<CacheWriteErr> for DrawToFrameError {
fn from(err: CacheWriteErr) -> Self {
DrawToFrameError::RendererFill(err)
}
}
impl StdError for BuildError {
fn cause(&self) -> Option<&dyn StdError> {
match *self {
BuildError::InvalidWindow => None,
BuildError::FailedToLoadFont(ref err) => Some(err),
}
}
}
impl StdError for DrawToFrameError {
fn cause(&self) -> Option<&dyn StdError> {
match *self {
DrawToFrameError::InvalidWindow => None,
DrawToFrameError::RendererPoisoned => None,
DrawToFrameError::RenderModePoisoned => None,
DrawToFrameError::InvalidRenderMode => None,
DrawToFrameError::RendererFill(ref err) => Some(err),
}
}
}
impl fmt::Display for BuildError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
BuildError::InvalidWindow => {
write!(f, "no open window associated with the given `window_id`")
}
BuildError::FailedToLoadFont(ref err) => fmt::Display::fmt(err, f),
}
}
}
impl fmt::Display for DrawToFrameError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match *self {
DrawToFrameError::InvalidWindow => {
"no open window associated with the given `window_id`"
}
DrawToFrameError::RendererPoisoned => "`Mutex` containing `Renderer` was poisoned",
DrawToFrameError::RenderModePoisoned => "`Mutex` containing `RenderMode` was poisoned",
DrawToFrameError::InvalidRenderMode => {
"`draw_to_frame` was called while `Ui` was in `Subpass` render mode"
}
DrawToFrameError::RendererFill(ref err) => return fmt::Display::fmt(err, f),
};
write!(f, "{}", s)
}
}
fn default_font(default_font_path: Option<&Path>) -> Result<Font, text::font::Error> {
fn conv_err(err: font::Error) -> text::font::Error {
match err {
font::Error::Io(err) => text::font::Error::IO(err),
font::Error::NoFont => text::font::Error::NoFont,
}
}
let font = match default_font_path {
None => {
#[cfg(feature = "notosans")]
{
font::default_notosans()
}
#[cfg(not(feature = "notosans"))]
{
match crate::app::find_assets_path() {
Err(_err) => return Err(text::font::Error::NoFont)?,
Ok(assets) => font::default(&assets).map_err(conv_err)?,
}
}
}
Some(path) => {
let font = font::from_file(path).map_err(conv_err)?;
font
}
};
Ok(font)
}