2016-12-11 01:44:13 -05:00
|
|
|
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
//! The display subsystem including window management, font rasterization, and
|
|
|
|
//! GPU drawing.
|
|
|
|
use std::sync::mpsc;
|
|
|
|
|
|
|
|
use parking_lot::{MutexGuard};
|
|
|
|
|
|
|
|
use Rgb;
|
|
|
|
use cli;
|
|
|
|
use config::Config;
|
2016-12-31 14:17:02 -05:00
|
|
|
use font::{self, Rasterize};
|
2016-12-11 01:44:13 -05:00
|
|
|
use meter::Meter;
|
2017-01-01 22:19:15 -05:00
|
|
|
use renderer::{self, GlyphCache, QuadRenderer};
|
2018-09-30 17:54:08 -04:00
|
|
|
use term::{Term, SizeInfo, RenderableCell};
|
2018-09-23 12:21:47 -04:00
|
|
|
use sync::FairMutex;
|
2016-12-11 01:44:13 -05:00
|
|
|
|
|
|
|
use window::{self, Size, Pixels, Window, SetInnerSize};
|
|
|
|
|
2016-12-31 22:49:51 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Error {
|
|
|
|
/// Error with window management
|
|
|
|
Window(window::Error),
|
|
|
|
|
|
|
|
/// Error dealing with fonts
|
|
|
|
Font(font::Error),
|
2017-01-01 22:19:15 -05:00
|
|
|
|
|
|
|
/// Error in renderer
|
|
|
|
Render(renderer::Error),
|
2016-12-31 22:49:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ::std::error::Error for Error {
|
|
|
|
fn cause(&self) -> Option<&::std::error::Error> {
|
|
|
|
match *self {
|
|
|
|
Error::Window(ref err) => Some(err),
|
|
|
|
Error::Font(ref err) => Some(err),
|
2017-01-01 22:19:15 -05:00
|
|
|
Error::Render(ref err) => Some(err),
|
2016-12-31 22:49:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn description(&self) -> &str {
|
|
|
|
match *self {
|
|
|
|
Error::Window(ref err) => err.description(),
|
|
|
|
Error::Font(ref err) => err.description(),
|
2017-01-01 22:19:15 -05:00
|
|
|
Error::Render(ref err) => err.description(),
|
2016-12-31 22:49:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ::std::fmt::Display for Error {
|
|
|
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
|
|
|
match *self {
|
|
|
|
Error::Window(ref err) => err.fmt(f),
|
|
|
|
Error::Font(ref err) => err.fmt(f),
|
2017-01-01 22:19:15 -05:00
|
|
|
Error::Render(ref err) => err.fmt(f),
|
2016-12-31 22:49:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<window::Error> for Error {
|
|
|
|
fn from(val: window::Error) -> Error {
|
|
|
|
Error::Window(val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<font::Error> for Error {
|
|
|
|
fn from(val: font::Error) -> Error {
|
|
|
|
Error::Font(val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-01 22:19:15 -05:00
|
|
|
impl From<renderer::Error> for Error {
|
|
|
|
fn from(val: renderer::Error) -> Error {
|
|
|
|
Error::Render(val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-11 01:44:13 -05:00
|
|
|
/// The display wraps a window, font rasterizer, and GPU renderer
|
2016-12-12 12:31:48 -05:00
|
|
|
pub struct Display {
|
2016-12-11 01:44:13 -05:00
|
|
|
window: Window,
|
|
|
|
renderer: QuadRenderer,
|
|
|
|
glyph_cache: GlyphCache,
|
|
|
|
render_timer: bool,
|
|
|
|
rx: mpsc::Receiver<(u32, u32)>,
|
|
|
|
tx: mpsc::Sender<(u32, u32)>,
|
|
|
|
meter: Meter,
|
2018-01-04 22:22:58 -05:00
|
|
|
font_size: font::Size,
|
2016-12-11 01:44:13 -05:00
|
|
|
size_info: SizeInfo,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Can wakeup the render loop from other threads
|
|
|
|
pub struct Notifier(window::Proxy);
|
|
|
|
|
2016-12-12 12:31:48 -05:00
|
|
|
/// Types that are interested in when the display is resized
|
|
|
|
pub trait OnResize {
|
|
|
|
fn on_resize(&mut self, size: &SizeInfo);
|
|
|
|
}
|
|
|
|
|
2016-12-11 01:44:13 -05:00
|
|
|
impl Notifier {
|
|
|
|
pub fn notify(&self) {
|
|
|
|
self.0.wakeup_event_loop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-12 12:31:48 -05:00
|
|
|
impl Display {
|
2016-12-11 01:44:13 -05:00
|
|
|
pub fn notifier(&self) -> Notifier {
|
|
|
|
Notifier(self.window.create_window_proxy())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update_config(&mut self, config: &Config) {
|
|
|
|
self.render_timer = config.render_timer();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get size info about the display
|
|
|
|
pub fn size(&self) -> &SizeInfo {
|
|
|
|
&self.size_info
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new(
|
|
|
|
config: &Config,
|
|
|
|
options: &cli::Options,
|
2016-12-31 22:49:51 -05:00
|
|
|
) -> Result<Display, Error> {
|
2016-12-11 01:44:13 -05:00
|
|
|
// Extract some properties from config
|
|
|
|
let render_timer = config.render_timer();
|
|
|
|
|
|
|
|
// Create the window where Alacritty will be displayed
|
2018-07-21 13:17:41 -04:00
|
|
|
let mut window = Window::new(&options, config.window())?;
|
2016-12-11 01:44:13 -05:00
|
|
|
|
2017-11-11 12:42:26 -05:00
|
|
|
// get window properties for initializing the other subsystems
|
2017-10-19 15:30:03 -04:00
|
|
|
let mut viewport_size = window.inner_size_pixels()
|
2016-12-31 22:49:51 -05:00
|
|
|
.expect("glutin returns window size");
|
2018-07-21 13:17:41 -04:00
|
|
|
let dpr = if config.font().scale_with_dpi() {
|
|
|
|
window.hidpi_factor()
|
|
|
|
} else {
|
|
|
|
1.0
|
|
|
|
};
|
2016-12-11 01:44:13 -05:00
|
|
|
|
2017-01-13 02:15:06 -05:00
|
|
|
info!("device_pixel_ratio: {}", dpr);
|
2016-12-11 01:44:13 -05:00
|
|
|
|
|
|
|
// Create renderer
|
2017-11-29 18:38:29 -05:00
|
|
|
let mut renderer = QuadRenderer::new(config, viewport_size)?;
|
2016-12-11 01:44:13 -05:00
|
|
|
|
2017-10-14 13:35:56 -04:00
|
|
|
let (glyph_cache, cell_width, cell_height) =
|
2018-07-21 13:17:41 -04:00
|
|
|
Self::new_glyph_cache(dpr, &mut renderer, config)?;
|
2016-12-11 01:44:13 -05:00
|
|
|
|
2017-10-19 15:30:03 -04:00
|
|
|
|
2017-02-05 05:01:26 -05:00
|
|
|
let dimensions = options.dimensions()
|
|
|
|
.unwrap_or_else(|| config.dimensions());
|
2017-11-11 12:42:26 -05:00
|
|
|
|
2017-10-19 15:30:03 -04:00
|
|
|
// Resize window to specified dimensions unless one or both dimensions are 0
|
|
|
|
if dimensions.columns_u32() > 0 && dimensions.lines_u32() > 0 {
|
|
|
|
let width = cell_width as u32 * dimensions.columns_u32();
|
|
|
|
let height = cell_height as u32 * dimensions.lines_u32();
|
2017-11-11 12:42:26 -05:00
|
|
|
|
2017-10-19 15:30:03 -04:00
|
|
|
let new_viewport_size = Size {
|
2018-05-11 14:22:36 -04:00
|
|
|
width: Pixels(width + 2 * u32::from(config.padding().x)),
|
|
|
|
height: Pixels(height + 2 * u32::from(config.padding().y)),
|
2017-10-19 15:30:03 -04:00
|
|
|
};
|
2017-11-11 12:42:26 -05:00
|
|
|
|
2017-10-19 15:30:03 -04:00
|
|
|
window.set_inner_size(&new_viewport_size);
|
|
|
|
renderer.resize(new_viewport_size.width.0 as _, new_viewport_size.height.0 as _);
|
|
|
|
viewport_size = new_viewport_size
|
|
|
|
}
|
|
|
|
|
2017-01-13 02:15:06 -05:00
|
|
|
info!("Cell Size: ({} x {})", cell_width, cell_height);
|
2016-12-11 01:44:13 -05:00
|
|
|
|
|
|
|
let size_info = SizeInfo {
|
2017-05-06 11:45:23 -04:00
|
|
|
width: viewport_size.width.0 as f32,
|
|
|
|
height: viewport_size.height.0 as f32,
|
2016-12-11 01:44:13 -05:00
|
|
|
cell_width: cell_width as f32,
|
2017-05-06 11:45:23 -04:00
|
|
|
cell_height: cell_height as f32,
|
2018-05-11 14:22:36 -04:00
|
|
|
padding_x: f32::from(config.padding().x),
|
|
|
|
padding_y: f32::from(config.padding().y),
|
2016-12-11 01:44:13 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
// Channel for resize events
|
|
|
|
//
|
|
|
|
// macOS has a callback for getting resize events, the channel is used
|
|
|
|
// to queue resize events until the next draw call. Unfortunately, it
|
|
|
|
// seems that the event loop is blocked until the window is done
|
|
|
|
// resizing. If any drawing were to happen during a resize, it would
|
|
|
|
// need to be in the callback.
|
|
|
|
let (tx, rx) = mpsc::channel();
|
|
|
|
|
2017-02-15 00:33:57 -05:00
|
|
|
// Clear screen
|
2017-07-28 18:14:18 -04:00
|
|
|
let background_color = config.colors().primary.background;
|
|
|
|
renderer.with_api(config, &size_info, 0. /* visual bell intensity */, |api| {
|
|
|
|
api.clear(background_color);
|
|
|
|
});
|
2017-02-15 00:33:57 -05:00
|
|
|
|
2017-07-20 13:50:50 -04:00
|
|
|
Ok(Display {
|
2018-03-04 17:40:15 -05:00
|
|
|
window,
|
|
|
|
renderer,
|
|
|
|
glyph_cache,
|
|
|
|
render_timer,
|
|
|
|
tx,
|
|
|
|
rx,
|
2016-12-11 01:44:13 -05:00
|
|
|
meter: Meter::new(),
|
2018-01-04 22:22:58 -05:00
|
|
|
font_size: font::Size::new(0.),
|
2018-03-04 17:40:15 -05:00
|
|
|
size_info,
|
2017-07-20 13:50:50 -04:00
|
|
|
})
|
2016-12-11 01:44:13 -05:00
|
|
|
}
|
|
|
|
|
2018-07-21 13:17:41 -04:00
|
|
|
fn new_glyph_cache(dpr: f32, renderer: &mut QuadRenderer, config: &Config)
|
2017-10-14 13:35:56 -04:00
|
|
|
-> Result<(GlyphCache, f32, f32), Error>
|
|
|
|
{
|
2018-01-04 22:22:58 -05:00
|
|
|
let font = config.font().clone();
|
2017-10-14 13:35:56 -04:00
|
|
|
let rasterizer = font::Rasterizer::new(dpr, config.use_thin_strokes())?;
|
|
|
|
|
|
|
|
// Initialize glyph cache
|
|
|
|
let glyph_cache = {
|
|
|
|
info!("Initializing glyph cache");
|
|
|
|
let init_start = ::std::time::Instant::now();
|
|
|
|
|
|
|
|
let cache = renderer.with_loader(|mut api| {
|
|
|
|
GlyphCache::new(rasterizer, &font, &mut api)
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let stop = init_start.elapsed();
|
2018-01-05 20:42:55 -05:00
|
|
|
let stop_f = stop.as_secs() as f64 + f64::from(stop.subsec_nanos()) / 1_000_000_000f64;
|
2017-10-14 13:35:56 -04:00
|
|
|
info!("Finished initializing glyph cache in {}", stop_f);
|
|
|
|
|
|
|
|
cache
|
|
|
|
};
|
|
|
|
|
|
|
|
// Need font metrics to resize the window properly. This suggests to me the
|
|
|
|
// font metrics should be computed before creating the window in the first
|
|
|
|
// place so that a resize is not needed.
|
|
|
|
let metrics = glyph_cache.font_metrics();
|
2018-05-11 14:22:36 -04:00
|
|
|
let cell_width = metrics.average_advance as f32 + f32::from(font.offset().x);
|
|
|
|
let cell_height = metrics.line_height as f32 + f32::from(font.offset().y);
|
2017-10-14 13:35:56 -04:00
|
|
|
|
2018-03-13 02:07:40 -04:00
|
|
|
// Prevent invalid cell sizes
|
|
|
|
if cell_width < 1. || cell_height < 1. {
|
|
|
|
panic!("font offset is too small");
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((glyph_cache, cell_width.floor(), cell_height.floor()))
|
2017-10-14 13:35:56 -04:00
|
|
|
}
|
|
|
|
|
2018-01-04 22:22:58 -05:00
|
|
|
pub fn update_glyph_cache(&mut self, config: &Config) {
|
2017-10-14 21:58:19 -04:00
|
|
|
let cache = &mut self.glyph_cache;
|
2018-01-04 22:22:58 -05:00
|
|
|
let size = self.font_size;
|
2017-10-14 21:58:19 -04:00
|
|
|
self.renderer.with_loader(|mut api| {
|
2018-01-04 22:22:58 -05:00
|
|
|
let _ = cache.update_font_size(config.font(), size, &mut api);
|
2017-10-14 21:58:19 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
let metrics = cache.font_metrics();
|
2018-01-05 20:42:55 -05:00
|
|
|
self.size_info.cell_width = ((metrics.average_advance + f64::from(config.font().offset().x)) as f32).floor();
|
|
|
|
self.size_info.cell_height = ((metrics.line_height + f64::from(config.font().offset().y)) as f32).floor();
|
2017-10-14 13:35:56 -04:00
|
|
|
}
|
|
|
|
|
2016-12-11 01:44:13 -05:00
|
|
|
#[inline]
|
|
|
|
pub fn resize_channel(&self) -> mpsc::Sender<(u32, u32)> {
|
|
|
|
self.tx.clone()
|
|
|
|
}
|
|
|
|
|
2017-02-22 15:46:08 -05:00
|
|
|
pub fn window(&mut self) -> &mut Window {
|
|
|
|
&mut self.window
|
2016-12-11 01:44:13 -05:00
|
|
|
}
|
|
|
|
|
2016-12-12 12:31:48 -05:00
|
|
|
/// Process pending resize events
|
2016-12-17 01:48:04 -05:00
|
|
|
pub fn handle_resize(
|
|
|
|
&mut self,
|
|
|
|
terminal: &mut MutexGuard<Term>,
|
2017-10-14 13:35:56 -04:00
|
|
|
config: &Config,
|
2016-12-17 01:48:04 -05:00
|
|
|
items: &mut [&mut OnResize]
|
|
|
|
) {
|
2016-12-11 01:44:13 -05:00
|
|
|
// Resize events new_size and are handled outside the poll_events
|
|
|
|
// iterator. This has the effect of coalescing multiple resize
|
|
|
|
// events into one.
|
|
|
|
let mut new_size = None;
|
|
|
|
|
2016-12-12 12:31:48 -05:00
|
|
|
// Take most recent resize event, if any
|
2016-12-11 01:44:13 -05:00
|
|
|
while let Ok(sz) = self.rx.try_recv() {
|
|
|
|
new_size = Some(sz);
|
|
|
|
}
|
|
|
|
|
2018-01-04 22:22:58 -05:00
|
|
|
// Font size modification detected
|
|
|
|
if terminal.font_size != self.font_size {
|
|
|
|
self.font_size = terminal.font_size;
|
|
|
|
self.update_glyph_cache(config);
|
2017-10-14 13:35:56 -04:00
|
|
|
|
|
|
|
if new_size == None {
|
|
|
|
// Force a resize to refresh things
|
|
|
|
new_size = Some((self.size_info.width as u32,
|
|
|
|
self.size_info.height as u32));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-11 01:44:13 -05:00
|
|
|
// Receive any resize events; only call gl::Viewport on last
|
|
|
|
// available
|
|
|
|
if let Some((w, h)) = new_size.take() {
|
2017-10-14 13:35:56 -04:00
|
|
|
self.size_info.width = w as f32;
|
|
|
|
self.size_info.height = h as f32;
|
|
|
|
|
|
|
|
let size = &self.size_info;
|
|
|
|
terminal.resize(size);
|
2016-12-12 12:31:48 -05:00
|
|
|
|
2017-09-27 20:29:44 -04:00
|
|
|
for item in items {
|
2016-12-12 12:31:48 -05:00
|
|
|
item.on_resize(size)
|
|
|
|
}
|
|
|
|
|
2017-07-20 13:50:50 -04:00
|
|
|
self.window.resize(w, h);
|
2016-12-11 01:44:13 -05:00
|
|
|
self.renderer.resize(w as i32, h as i32);
|
|
|
|
}
|
|
|
|
|
2016-12-12 12:31:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Draw the screen
|
|
|
|
///
|
|
|
|
/// A reference to Term whose state is being drawn must be provided.
|
|
|
|
///
|
|
|
|
/// This call may block if vsync is enabled
|
2018-09-23 12:21:47 -04:00
|
|
|
pub fn draw(&mut self, terminal: &FairMutex<Term>, config: &Config) {
|
|
|
|
let mut terminal = terminal.lock();
|
2018-09-30 17:54:08 -04:00
|
|
|
let size_info = *terminal.size_info();
|
|
|
|
let visual_bell_intensity = terminal.visual_bell.intensity();
|
|
|
|
let background_color = terminal.background_color();
|
|
|
|
|
|
|
|
let window_focused = self.window.is_focused;
|
|
|
|
let grid_cells: Vec<RenderableCell> = terminal
|
|
|
|
.renderable_cells(config, window_focused)
|
|
|
|
.collect();
|
2018-09-23 12:21:47 -04:00
|
|
|
|
2016-12-12 12:31:48 -05:00
|
|
|
// Clear dirty flag
|
2017-02-03 18:34:52 -05:00
|
|
|
terminal.dirty = !terminal.visual_bell.completed();
|
2016-12-12 12:31:48 -05:00
|
|
|
|
2017-01-11 00:21:19 -05:00
|
|
|
if let Some(title) = terminal.get_next_title() {
|
|
|
|
self.window.set_title(&title);
|
|
|
|
}
|
|
|
|
|
2017-12-24 15:15:42 -05:00
|
|
|
if let Some(mouse_cursor) = terminal.get_next_mouse_cursor() {
|
|
|
|
self.window.set_mouse_cursor(mouse_cursor);
|
|
|
|
}
|
|
|
|
|
2017-10-21 19:03:58 -04:00
|
|
|
if let Some(is_urgent) = terminal.next_is_urgent.take() {
|
|
|
|
// We don't need to set the urgent flag if we already have the
|
|
|
|
// user's attention.
|
|
|
|
if !is_urgent || !self.window.is_focused {
|
|
|
|
self.window.set_urgent(is_urgent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-30 17:54:08 -04:00
|
|
|
// Clear when terminal mutex isn't held. Mesa for
|
|
|
|
// some reason takes a long time to call glClear(). The driver descends
|
|
|
|
// into xcb_connect_to_fd() which ends up calling __poll_nocancel()
|
|
|
|
// which blocks for a while.
|
|
|
|
//
|
|
|
|
// By keeping this outside of the critical region, the Mesa bug is
|
|
|
|
// worked around to some extent. Since this doesn't actually address the
|
|
|
|
// issue of glClear being slow, less time is available for input
|
|
|
|
// handling and rendering.
|
|
|
|
drop(terminal);
|
|
|
|
|
|
|
|
self.renderer.with_api(config, &size_info, visual_bell_intensity, |api| {
|
|
|
|
api.clear(background_color);
|
|
|
|
});
|
|
|
|
|
2016-12-11 01:44:13 -05:00
|
|
|
{
|
|
|
|
let glyph_cache = &mut self.glyph_cache;
|
2017-02-11 15:49:40 -05:00
|
|
|
|
2016-12-11 01:44:13 -05:00
|
|
|
// Draw grid
|
|
|
|
{
|
|
|
|
let _sampler = self.meter.sampler();
|
|
|
|
|
2017-02-11 15:49:40 -05:00
|
|
|
self.renderer.with_api(config, &size_info, visual_bell_intensity, |mut api| {
|
2016-12-11 01:44:13 -05:00
|
|
|
// Draw the grid
|
2018-09-30 17:54:08 -04:00
|
|
|
api.render_cells(grid_cells.iter(), glyph_cache);
|
2016-12-11 01:44:13 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw render timer
|
|
|
|
if self.render_timer {
|
|
|
|
let timing = format!("{:.3} usec", self.meter.average());
|
2017-02-11 15:49:40 -05:00
|
|
|
let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 };
|
|
|
|
self.renderer.with_api(config, &size_info, visual_bell_intensity, |mut api| {
|
|
|
|
api.render_string(&timing[..], glyph_cache, color);
|
2016-12-11 01:44:13 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-31 22:49:51 -05:00
|
|
|
self.window
|
|
|
|
.swap_buffers()
|
|
|
|
.expect("swap buffers");
|
2016-12-11 01:44:13 -05:00
|
|
|
}
|
2017-05-27 23:08:28 -04:00
|
|
|
|
|
|
|
pub fn get_window_id(&self) -> Option<usize> {
|
|
|
|
self.window.get_window_id()
|
|
|
|
}
|
2017-07-24 20:54:06 -04:00
|
|
|
|
2018-05-14 23:21:06 -04:00
|
|
|
/// Adjust the IME editor position according to the new location of the cursor
|
2017-07-24 20:54:06 -04:00
|
|
|
pub fn update_ime_position(&mut self, terminal: &Term) {
|
|
|
|
use index::{Point, Line, Column};
|
|
|
|
use term::SizeInfo;
|
|
|
|
let Point{line: Line(row), col: Column(col)} = terminal.cursor().point;
|
|
|
|
let SizeInfo{cell_width: cw,
|
|
|
|
cell_height: ch,
|
|
|
|
padding_x: px,
|
|
|
|
padding_y: py, ..} = *terminal.size_info();
|
2018-05-14 23:21:06 -04:00
|
|
|
let nspot_y = (py + (row + 1) as f32 * ch) as i32;
|
|
|
|
let nspot_x = (px + col as f32 * cw) as i32;
|
|
|
|
self.window().set_ime_spot(nspot_x, nspot_y);
|
2017-07-24 20:54:06 -04:00
|
|
|
}
|
2016-12-11 01:44:13 -05:00
|
|
|
}
|
2018-09-30 17:54:08 -04:00
|
|
|
|