use std::sync::Arc;
use rust_i18n::t;
use crate::file_chooser::view::icon;
use crate::{ button, column, container, dialog, flex, icon_button, img_widget, list_item, row, scroll, separator, spacer, text, text_edit, toggle };
use crate::{ ButtonVariant, Element, Length };
use super::{ Margins, Orientation, PrintDialog, PrintDialogMsg, Printer, PrinterState, Section, Sides, PAGES, PAPERS };
const ROW_H: f32 = 44.0;
const TOOL: f32 = 40.0;
const TOOL_ICON: f32 = 22.0;
const INDENT: f32 = 32.0;
const CARD_W: f32 = 880.0;
const SHEET: ( f32, f32 ) = ( 320.0, 420.0 );
const SHEET_COMPACT: ( f32, f32 ) = ( 240.0, 260.0 );
const CLOSE: &str = "navigation/close";
const PREVIOUS: &str = "general/left-simple";
const NEXT: &str = "general/right-simple";
const LESS: &str = "actions/remove";
const MORE: &str = "actions/add";
const PRINTER: &str = "hardware/printer";
const PDF: &str = "archives/pdf";
pub( super ) fn view<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg> ) -> Element<Msg>
{
if d.compact { return compact( d ); }
let mut body = row().spacing( Length::widget( 16.0 ) ).align_top();
if let Some( preview ) = preview( d, SHEET ) { body = body.push( preview ); }
dialog()
.title( t!( "print.title" ).to_string() )
.subtitle( d.title.clone() )
.body( body.push( flex( settings( d ) ) ) )
.cancel( d.msg( PrintDialogMsg::Cancel ) )
.action( cancel_button( d ) )
.action( print_button( d ) )
.max_width( Length::widget( CARD_W ) )
.into()
}
fn compact<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg> ) -> Element<Msg>
{
let palette = crate::theme::palette();
let titles = column()
.padding( 0.0 )
.spacing( 0.0 )
.align_center_x( false )
.push( text( t!( "print.title" ).to_string() ).size( 15.0 ).weight( 600 ).color( palette.text_primary ) )
.push( text( d.title.clone() ).size( 12.0 ).color( palette.text_secondary ) );
let header = row()
.padding( Length::widget( 4.0 ) )
.spacing( Length::widget( 4.0 ) )
.push( tool( d, CLOSE, t!( "print.cancel" ).to_string(), Some( PrintDialogMsg::Cancel ) ) )
.push( flex( titles ) );
let mut content = column().padding( Length::widget( 8.0 ) ).spacing( Length::widget( 8.0 ) );
if let Some( preview ) = preview( d, SHEET_COMPACT )
{
content = content.push( row().spacing( 0.0 ).push( flex( spacer() ) ).push( preview ).push( flex( spacer() ) ) );
}
let actions = row()
.padding( Length::widget( 8.0 ) )
.spacing( Length::widget( 8.0 ) )
.align_right()
.push( cancel_button( d ) )
.push( print_button( d ) );
let page = column()
.padding( 0.0 )
.spacing( 0.0 )
.push( container( header ).background( palette.surface_alt ) )
.push( separator() )
.push( flex( scroll( content.push( settings( d ) ) ) ) )
.push( separator() )
.push( actions );
container( page ).background( palette.bg ).into()
}
fn preview<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg>, ( box_w, box_h ): ( f32, f32 ) ) -> Option<Element<Msg>>
{
if !d.previews { return None; }
let palette = crate::theme::palette();
let mut pane = column().fit_content().align_center_x( true ).padding( 0.0 ).spacing( Length::widget( 8.0 ) );
match d.images.get( d.shown )
{
Some( ( rgba, width, height ) ) =>
{
let scale = ( box_w / *width as f32 ).min( box_h / *height as f32 );
let page = img_widget( Arc::clone( rgba ), *width, *height )
.size( Length::widget( *width as f32 * scale ), Length::widget( *height as f32 * scale ) );
let sheet = container( page ).border( palette.divider, 1.0 ).opacity( if d.rendering.is_some() { 0.6 } else { 1.0 } );
pane = pane.push( sheet ).push( pager( d ) );
}
None =>
{
let message = match &d.preview_error
{
Some( error ) => error.clone(),
None => t!( "print.preparing" ).to_string(),
};
let placeholder = column()
.fit_content()
.align_center_x( true )
.padding( 0.0 )
.spacing( 0.0 )
.push( spacer().width( Length::widget( box_w ) ).height( Length::widget( box_h / 2.0 - 10.0 ) ) )
.push( container( text( message ).size( 13.0 ).color( palette.text_secondary ).wrap( true ).align_center() ).max_width( box_w ) )
.push( spacer().height( Length::widget( box_h / 2.0 - 10.0 ) ) );
pane = pane.push( container( placeholder ).border( palette.divider, 1.0 ) );
}
}
Some( pane.into() )
}
fn pager<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg> ) -> Element<Msg>
{
let palette = crate::theme::palette();
let count = d.images.len();
let mut position = t!( "print.page_of", page = d.shown + 1, count = count ).to_string();
if let Some( total ) = d.preview.as_ref().map( |p| p.total ).filter( |total| *total != count )
{
position = format!( "{position} · {}", t!( "print.total", count = total ) );
}
row()
.spacing( Length::widget( 4.0 ) )
.push( tool( d, PREVIOUS, t!( "print.previous" ).to_string(), ( d.shown > 0 ).then_some( PrintDialogMsg::Page( -1 ) ) ) )
.push( text( position ).size( 13.0 ).color( palette.text_secondary ) )
.push( tool( d, NEXT, t!( "print.next" ).to_string(), ( d.shown + 1 < count ).then_some( PrintDialogMsg::Page( 1 ) ) ) )
.into()
}
fn settings<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg> ) -> Element<Msg>
{
let palette = crate::theme::palette();
let options = &d.options;
let printing = d.destination.is_some();
let mut list = column().padding( 0.0 ).spacing( 0.0 ).align_center_x( false );
let target = match d.destination.and_then( |i| d.printers.get( i ) )
{
Some( printer ) => printer.description.clone(),
None => t!( "print.pdf" ).to_string(),
};
list = list.push( choice( d, t!( "print.destination" ).to_string(), target, Section::Destination ) );
if d.open == Some( Section::Destination )
{
for ( i, printer ) in d.printers.iter().enumerate()
{
let status = status( printer );
list = list.push( item( d, printer.description.clone(), Some( PRINTER ), ( !status.is_empty() ).then_some( status ), d.destination == Some( i ), PrintDialogMsg::Destination( Some( i ) ) ) );
}
list = list.push( item( d, t!( "print.pdf" ).to_string(), Some( PDF ), None, !printing, PrintDialogMsg::Destination( None ) ) );
if d.listing
{
list = list.push( note( t!( "print.searching" ).to_string() ) );
} else if let Some( error ) = &d.list_error {
list = list.push( note( error.clone() ) );
}
}
if printing
{
let copies = options.copies;
list = list.push( stepper( d, t!( "print.copies" ).to_string(), copies.to_string(),
( copies > 1 ).then_some( PrintDialogMsg::Copies( -1 ) ),
( copies < super::PrintOptions::MAX_COPIES ).then_some( PrintDialogMsg::Copies( 1 ) ) ) );
}
list = list
.push( heading( t!( "print.pages" ).to_string() ) )
.push( item( d, t!( "print.all_pages" ).to_string(), None, None, !d.some_pages, PrintDialogMsg::AllPages ) )
.push( item( d, t!( "print.some_pages" ).to_string(), None, None, d.some_pages, PrintDialogMsg::SomePages ) );
if d.some_pages
{
let wrap = Arc::clone( &d.wrap );
let field = text_edit( t!( "print.pages_hint" ).to_string(), d.pages_text.clone() )
.id( PAGES )
.on_change( move |text| wrap( PrintDialogMsg::Pages( text ) ) )
.on_submit( d.msg( PrintDialogMsg::Print ) );
list = list.push( container( field ).padding_left( Length::widget( INDENT ) ).padding_right( Length::widget( 8.0 ) ) );
if d.pages_bad
{
let hint = text( t!( "print.pages_bad" ).to_string() ).size( 12.0 ).color( palette.danger ).wrap( true );
list = list.push( container( hint ).padding_left( Length::widget( INDENT ) ).padding_v( Length::widget( 4.0 ) ) );
}
}
list = list.push( choice( d, t!( "print.paper" ).to_string(), options.paper.name.to_string(), Section::Paper ) );
if d.open == Some( Section::Paper )
{
for ( i, paper ) in PAPERS.iter().enumerate()
{
list = list.push( item( d, paper.name.to_string(), None, None, options.paper == *paper, PrintDialogMsg::Paper( i ) ) );
}
}
list = list.push( choice( d, t!( "print.orientation" ).to_string(), orientation_label( options.orientation ), Section::Orientation ) );
if d.open == Some( Section::Orientation )
{
for orientation in [ Orientation::Portrait, Orientation::Landscape ]
{
list = list.push( item( d, orientation_label( orientation ), None, None, options.orientation == orientation, PrintDialogMsg::Orientation( orientation ) ) );
}
}
list = list.push( choice( d, t!( "print.margins" ).to_string(), margins_label( options.margins ), Section::Margins ) );
if d.open == Some( Section::Margins )
{
for margins in Margins::ALL
{
list = list.push( item( d, margins_label( margins ), None, None, options.margins == margins, PrintDialogMsg::Margins( margins ) ) );
}
}
let percent = ( options.scale * 100.0 ).round() as i32;
list = list.push( stepper( d, t!( "print.scale" ).to_string(), format!( "{percent} %" ),
( options.scale > super::PrintOptions::MIN_SCALE + f32::EPSILON ).then_some( PrintDialogMsg::Scale( -1 ) ),
( options.scale < super::PrintOptions::MAX_SCALE - f32::EPSILON ).then_some( PrintDialogMsg::Scale( 1 ) ) ) );
if printing
{
list = list.push( choice( d, t!( "print.sides" ).to_string(), sides_label( options.sides ), Section::Sides ) );
if d.open == Some( Section::Sides )
{
for sides in Sides::ALL
{
list = list.push( item( d, sides_label( sides ), None, None, options.sides == sides, PrintDialogMsg::Sides( sides ) ) );
}
}
list = list.push( switch( d, t!( "print.color" ).to_string(), options.color, PrintDialogMsg::Color ) );
}
list.push( switch( d, t!( "print.backgrounds" ).to_string(), options.backgrounds, PrintDialogMsg::Backgrounds ) ).into()
}
fn choice<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg>, label: String, value: String, section: Section ) -> Element<Msg>
{
let palette = crate::theme::palette();
list_item( label )
.height( Length::widget( ROW_H ) )
.ink( palette.text_primary, palette.text_secondary )
.trailing( value )
.selected( d.open == Some( section ) )
.on_press( d.msg( PrintDialogMsg::Toggle( section ) ) )
.into()
}
fn item<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg>, label: String, glyph: Option<&'static str>, below: Option<String>, selected: bool, msg: PrintDialogMsg ) -> Element<Msg>
{
let palette = crate::theme::palette();
let mut entry = list_item( label )
.height( Length::widget( if below.is_some() { 56.0 } else { ROW_H } ) )
.pad_h( Length::widget( INDENT ) )
.ink( palette.text_primary, palette.text_secondary )
.selected( selected )
.on_press( d.msg( msg ) );
if let Some( below ) = below { entry = entry.subtitle( below ); }
if let Some( ( rgba, w, h ) ) = glyph.and_then( |glyph| icon( glyph, palette.icon ) ) { entry = entry.icon( rgba, w, h ); }
entry.into()
}
fn stepper<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg>, label: String, value: String, less: Option<PrintDialogMsg>, more: Option<PrintDialogMsg> ) -> Element<Msg>
{
let palette = crate::theme::palette();
row()
.spacing( 0.0 )
.push( flex( container( text( label ).size( 15.0 ).color( palette.text_primary ) ).padding_h( Length::widget( 16.0 ) ) ) )
.push( tool( d, LESS, t!( "print.less" ).to_string(), less ) )
.push( text( value ).size( 14.0 ).color( palette.text_primary ) )
.push( tool( d, MORE, t!( "print.more" ).to_string(), more ) )
.into()
}
fn switch<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg>, label: String, on: bool, msg: PrintDialogMsg ) -> Element<Msg>
{
container( toggle( on ).label( label ).on_toggle( d.msg( msg ) ) )
.padding_h( Length::widget( 16.0 ) )
.padding_v( Length::widget( 6.0 ) )
.into()
}
fn heading<Msg: Clone + 'static>( label: String ) -> Element<Msg>
{
let palette = crate::theme::palette();
container( text( label ).size( 15.0 ).color( palette.text_primary ) )
.padding_h( Length::widget( 16.0 ) )
.padding_v( Length::widget( 12.0 ) )
.into()
}
fn note<Msg: Clone + 'static>( message: String ) -> Element<Msg>
{
container( text( message ).size( 13.0 ).color( crate::theme::palette().text_secondary ).wrap( true ) )
.padding_left( Length::widget( INDENT ) )
.padding_v( Length::widget( 8.0 ) )
.into()
}
fn tool<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg>, glyph: &'static str, label: String, msg: Option<PrintDialogMsg> ) -> Element<Msg>
{
let palette = crate::theme::palette();
let tint = if msg.is_some() { palette.icon } else { palette.divider };
let base = match icon( glyph, tint )
{
Some( ( rgba, w, h ) ) => icon_button( rgba, w, h ).icon_size( Length::widget( TOOL_ICON ) ),
None => button( label.clone() ),
};
base.variant( ButtonVariant::Tertiary )
.tooltip( label )
.width( Length::widget( TOOL ) )
.height( Length::widget( TOOL ) )
.on_press_maybe( msg.map( |msg| d.msg( msg ) ) )
.into()
}
fn cancel_button<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg> ) -> Element<Msg>
{
button( t!( "print.cancel" ).to_string() ).variant( ButtonVariant::Secondary ).on_press( d.msg( PrintDialogMsg::Cancel ) ).into()
}
fn print_button<Msg: Clone + Send + 'static>( d: &PrintDialog<Msg> ) -> Element<Msg>
{
let label = if d.destination.is_some() { t!( "print.print" ) } else { t!( "print.save" ) };
button( label.to_string() )
.variant( ButtonVariant::Primary )
.on_press_maybe( d.can_print().then( || d.msg( PrintDialogMsg::Print ) ) )
.into()
}
fn status( printer: &Printer ) -> String
{
let state = match ( printer.state, printer.accepting )
{
( _, false ) => t!( "print.rejecting" ).to_string(),
( PrinterState::Idle, _ ) => t!( "print.ready" ).to_string(),
( PrinterState::Printing, _ ) => t!( "print.busy" ).to_string(),
( PrinterState::Stopped, _ ) => t!( "print.stopped" ).to_string(),
( PrinterState::Unknown, _ ) => String::new(),
};
[ state, printer.location.clone() ].into_iter().filter( |s| !s.is_empty() ).collect::<Vec<_>>().join( " · " )
}
fn orientation_label( orientation: Orientation ) -> String
{
match orientation
{
Orientation::Portrait => t!( "print.portrait" ),
Orientation::Landscape => t!( "print.landscape" ),
}.to_string()
}
fn margins_label( margins: Margins ) -> String
{
match margins
{
Margins::Default => t!( "print.margins_default" ),
Margins::Narrow => t!( "print.margins_narrow" ),
Margins::Wide => t!( "print.margins_wide" ),
Margins::None => t!( "print.margins_none" ),
}.to_string()
}
fn sides_label( sides: Sides ) -> String
{
match sides
{
Sides::One => t!( "print.one_side" ),
Sides::LongEdge => t!( "print.long_edge" ),
Sides::ShortEdge => t!( "print.short_edge" ),
}.to_string()
}