class Cani::Api::Feature::Viewer
Constants
- COLORS
- COLOR_PAIRS
- COMPACT
- ERAS
- MARKET_SHARE_THRESHHOLD
- NOTE_COLORS
- PADDING
- PERCENT_COLORS
Attributes
browsers[R]
col_width[R]
feature[R]
height[R]
table_width[R]
viewable[R]
width[R]
Public Class Methods
new(feature, browsers = Cani.api.browsers)
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 99 def initialize(feature, browsers = Cani.api.browsers) @feature = feature @browsers = browsers @viewable = browsers.size resize Curses.init_screen Curses.curs_set 0 Curses.noecho if Curses.has_colors? Curses.use_default_colors Curses.start_color end COLOR_PAIRS.each do |(cn, clp)| Curses.init_pair cn, *clp end trap('INT', &method(:close)) at_exit(&method(:close)) end
Public Instance Methods
close(*_args)
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 123 def close(*_args) Curses.close_screen end
color(key, type = :bg, source = COLORS)
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 411 def color(key, type = :bg, source = COLORS) target = key.to_s.downcase.to_sym type = type.to_sym source.find { |(k, _)| k == target }.to_a .fetch(1, {}) .fetch(type, source[:default][type]) end
colw()
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 387 def colw colw = PADDING * 2 + browsers[0..viewable].map(&:max_column_width).max colw.even? ? colw : colw + 1 end
compact?()
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 407 def compact? width < COMPACT end
draw()
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 127 def draw Curses.clear outer_width = table_width + 2 percent_num = format '%.2f%%', feature.percent status_format = "[#{feature.status}]" percent_label = compact? ? '' : 'support: ' legend_format = 'legend'.center outer_width notes_format = 'notes'.center outer_width offset_x = ((width - outer_width) / 2.0).floor offset_y = 1 cy = 0 # positioning and drawing of percentage perc_num_xs = outer_width - percent_num.size Curses.setpos offset_y + cy, offset_x + perc_num_xs Curses.attron percent_color(feature.percent) do Curses.addstr percent_num end # positioning and drawing of 'support: ' text # ditch this part all together when in compact mode unless compact? perc_lbl_xs = perc_num_xs - percent_label.size Curses.setpos offset_y + cy, offset_x + perc_lbl_xs Curses.addstr percent_label end # draw possibly multi-line feature title title_size = [table_width - percent_num.size - percent_label.size - status_format.size - 3, 1].max title_size += status_format.size if compact? title_chunks = feature.title.chars.each_slice(title_size).map(&:join) title_chunks.each do |part| Curses.setpos offset_y + cy, offset_x Curses.addstr part cy += 1 end # status positioning and drawing # when compact? draw it on the second line instead of the # first line at the end of the title cy += 1 status_yp = offset_y + (compact? ? 1 : 0) status_xp = offset_x + (compact? ? table_width - status_format.size : [title_size, feature.title.size].min + 1) Curses.setpos status_yp, status_xp Curses.attron status_color(feature.status) do Curses.addstr status_format end # 'more or less' predict a height that is too small # since we don't know the entire height but draw # it line-by-line at the moment compact_height = height <= 40 # by default, notes are only shown if visible in the actual table # this means there might be more notes than actually displayed # but this allows us to optimally use available space to display # the most useful information to the user # TODO: provide a config setting to disable this behaviour notes_visible = [] # meaty part, loop through browsers to create # the final feature table relevant_era_count = browsers[0...viewable].map do |browser| era_idx = browser.most_popular_era_idx era_range = (era_idx - (ERAS / 2.0).floor + 1)..(era_idx + (ERAS / 2.0).ceil) era_range.map do |cur_era| era = browser.eras[cur_era].to_s browser.usage[era].to_f >= MARKET_SHARE_THRESHHOLD || (!era.empty? && cur_era >= era_idx - 1) end.select { |x| x }.size end.max browsers[0...viewable].each.with_index do |browser, x| # some set up to find the current era for each browser # and creating a range around that to show past / coming support era_idx = browser.most_popular_era_idx era_range = (era_idx - (relevant_era_count / 2.0).floor + 1)..(era_idx + (relevant_era_count / 2.0).ceil) bx = offset_x + x * col_width + x by = offset_y + cy # draw browser names Curses.setpos by, bx Curses.attron color(:header) do Curses.addstr browser.name.tr('_', '.').center(col_width) end # accordingly increment current browser y for the table header (browser names) # and an additional empty line below the table header by += 3 # draw era's for the current browser era_range.each.with_index do |cur_era, y| era = browser.eras[cur_era].to_s supp_type = feature.support_in(browser.name, era) colr = color supp_type is_current = cur_era == era_idx past_curr = cur_era > era_idx top_pad = 1 bot_pad = compact_height ? 0 : 1 ey = by + (y * (2 + top_pad + bot_pad)) + (bot_pad.zero? && past_curr ? 1 : 0) note_nums = feature.browser_note_nums.fetch(browser.name, {}) .fetch(era, []) # do not draw era's that exceed screen height break if (ey + (is_current ? 1 : bot_pad) + 1) >= height # draw current era outline before drawing all era cells on top if is_current Curses.setpos ey - top_pad - 1, [bx - 1, 0].max Curses.attron(color(:era_border)) { Curses.addstr ' ' * (col_width + 2) } Curses.setpos ey + (is_current ? 1 : bot_pad) + 1, [bx - 1, 0].max Curses.attron(color(:era_border)) { Curses.addstr ' ' * (col_width + 2) } end # only show visible / relevant browsers # era's can either be empty or too new to determine # their usefulness by usage (when newer than current era). if browser.usage[era].to_f >= MARKET_SHARE_THRESHHOLD || (!era.empty? && cur_era >= era_idx - 1) ((ey - top_pad)..(ey + (is_current ? 1 : bot_pad))).each do |ry| txt = (bot_pad.zero? && !is_current) ? (ry >= ey + (is_current ? 1 : bot_pad) ? era.to_s : ' ') : (ry == ey ? era.to_s : ' ') Curses.setpos ry, bx Curses.attron(colr) { Curses.addstr txt.center(col_width) } # draw current ara border inbetween the cells if is_current Curses.setpos ry, bx - 1 Curses.attron(color(:era_border)) { Curses.addstr ' ' } Curses.setpos ry, offset_x + table_width + 2 Curses.attron(color(:era_border)) { Curses.addstr ' ' } end end if note_nums.any? notes_visible.concat(note_nums).uniq! Curses.setpos ey - top_pad, bx Curses.attron(note_color(supp_type)) { Curses.addstr ' ' + note_nums.join(' ') } end end end end # increment current y by amount of eras # plus the 4 lines around the current era # plus the 1 line of browser names # plus the 2 blank lines above and below the eras cy += (relevant_era_count - 1) * (compact_height ? 3 : 4) + relevant_era_count + (relevant_era_count % 2 == 0 ? 0 : 1) if height > cy + 3 # print legend header Curses.setpos offset_y + cy, offset_x Curses.attron color(:header) do Curses.addstr legend_format end end # increment current y by 2 # one for the header line # plus one for a blank line below it cy += 2 # loop through all features to create a legend # showing which label belongs to which color if height > cy + 1 Feature::TYPES.values.each_slice viewable do |group| # draw legend texts at proper position group.compact.each.with_index do |type, lx| Curses.setpos offset_y + cy, offset_x + lx * col_width + lx Curses.attron color(type[:name], :fg) do Curses.addstr "#{type[:short]}(#{type[:symbol]})".center(col_width) end end # if there is more than one group, print the next # group on a new line cy += 1 end end # add extra empty line after legend cy += 1 notes_chunked = feature.notes.map { |nt| nt.chars.each_slice(outer_width).map(&:join).map(&:strip) } filter_vis = Cani.config.notes == 'relevant' ? notes_visible.map(&:to_s) : feature.notes_by_num.keys num_chunked = feature.notes_by_num .select { |(k, _)| filter_vis.include? k } .each_with_object({}) { |(k, nt), h| h[k] = nt.chars.each_slice(outer_width - 5).map(&:join).map(&:strip) } notes_total = (notes_chunked.map(&:size) + num_chunked.map(&:size)).reduce(0) { |total, add| total + add } if height > cy + 2 && (notes_chunked.any? || num_chunked.any?) # print notes header Curses.setpos offset_y + cy, offset_x Curses.attron color(:header) do Curses.addstr notes_format end end # add two new lines, one for the notes header # and one empty line below it cy += 2 # print global notes, wrapped on terminal width notes_chunked.each do |chunks| break if cy + 1 + chunks.size > height chunks.each do |part| Curses.setpos offset_y + cy, offset_x Curses.addstr part cy += 1 end cy += 1 end # print numbered notes, wrapped on terminal width num_chunked.each do |num, chunks| break if cy + 1 + chunks.size > height Curses.setpos offset_y + cy, offset_x Curses.attron color(:header) do Curses.addstr num.center(3) end chunks.each do |part| Curses.setpos offset_y + cy, offset_x + 5 Curses.addstr part cy += 1 end cy += 1 end Curses.refresh end
note_color(status)
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 420 def note_color(status) color status, :fg, NOTE_COLORS end
percent_color(percent)
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 428 def percent_color(percent) PERCENT_COLORS.find { |(r, _)| r.include? percent }.to_a .fetch(1, {}) .fetch(:fg, COLORS[:unknown][:fg]) end
render()
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 372 def render loop do Curses.clear draw key = Curses.getch case key when Curses::KEY_RESIZE then resize else break unless key.nil? end end close end
resize()
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 397 def resize @height, @width = IO.console.winsize @viewable = browsers.size @viewable -= 1 while tablew >= @width @col_width = [colw, Feature::TYPES.map { |(_, h)| h[:short].size }.max + 3].max @table_width = tablew - 2 # vertical padding at start and end of current era line end
status_color(status)
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 424 def status_color(status) color status, :fg end
tablew()
click to toggle source
# File lib/cani/api/feature/viewer.rb, line 393 def tablew colw * viewable + viewable - 1 end